# Run experiments on branches Source: https://docs.lancedb.com/agent-branch-experiments Use LanceDB branches to isolate agent-driven experiments from main, evaluate them on a fixed test set, and promote only the winner. LanceDB supports [branching](/tables/branching) to isolate experiments from the main table when working with agents. Branches are useful when you instruct an agent to try several approaches without affecting the production data on the `main` branch. Each experiment gets its own writable table history. For example, you can generate and compare embeddings from two text embedding models on the given table: | Branch | Model | Added column | | -------------- | ---------------------------------------- | --------------- | | `embed-minilm` | `sentence-transformers/all-MiniLM-L6-v2` | `vector_minilm` | | `embed-nomic` | `nomic-embed-text` from Ollama | `vector_nomic` | The branches share a logical table, but each has its own schema and history. The original table handle still points to `main`. LanceDB does not have a process-wide current branch. Install the model packages, then download the `nomic-embed-text` model from Ollama (or any other model you prefer): ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} uv add sentence-transformers ollama ollama pull nomic-embed-text ``` It's always recommended to measure the results of an experiment on a fixed evaluation set. Here's an example of how you could ask the agent to run both experiments and compare the results: ```text Agent prompt theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} Use the lancedb skill and the LanceDB branching documentation to compare two embedding experiments on the `camelot_multimodal` table. 1. Fork `embed-minilm` from `main`. Build normalized embeddings from `"{role}. {description}"` with `sentence-transformers/all-MiniLM-L6-v2` and add them as `vector_minilm`. 2. Fork `embed-nomic` from `main`. Embed the identical text with Ollama `nomic-embed-text` and add the results as `vector_nomic`. 3. Process rows in batches and write only through each branch-scoped table handle. Verify that neither new column appears on `main`. 4. Evaluate both branches with the queries "wise magical advisor", "treacherous rebel", and "virtuous Grail knight". Report the top three names, latency, and whether the expected character ranks first. 5. Do not modify `main` and do not delete either branch. Recommend a winner based on the results. ``` The vector dimensions may differ because the columns live on separate branches. Use a different column name for each model. Within an experiment, use the same model for the stored text and the search queries. ## Apply the winning experiment to `main` The branch experiments leave you with the results side by side, and they deliberately never touch `main`. Once you've picked a winner, apply it to `main` yourself by rerunning that experiment's validated transformation directly against the `main` table. Rerunning the reviewed operation is the reliable path on both OSS and Enterprise: it replays exactly the transformation you validated on the branch, and it works the same regardless of how the branch evolved. Suppose `nomic-embed-text` wins. Rerun the same Nomic embedding transformation you validated on `embed-nomic` against `main` in batches, then call `table.optimize()` on OSS. Verify a bounded vector search on `main`, and keep the losing branch around until you're confident in the result. See [Branches](/tables/branching) for more on how branches, versions, and tags relate. ## More experiments you can run Swapping embedding models on a branch while working with agents is only one example of what you can do with the LanceDB skill. The table below shows other experiments you can run with the skill. | Hypothesis | Change on the branch | What to evaluate | | ---------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------ | | A different embedding model improves retrieval | Add a new vector column | Recall, ranking quality, latency, and cost | | A new parser or OCR model improves source data | Reprocess files and backfill metadata | Validation failures and a reviewed sample | | A new search setup works better | Build an index or change hybrid-search and reranking settings | Relevance and latency on a fixed query set | | A curation rule improves the corpus | Deduplicate, classify, or filter records | False positives, false negatives, and row counts | | A migration is safe to deploy | Add columns or run a backfill | Schema compatibility and application checks | For each experiment, ask the agent to state the hypothesis, use a fixed evaluation set, report the comparison, and, if you want, work on an alternate branch that's isolated from `main` until you decide you want the new derived column in `main`. Once you've chosen a winner, rerun that validated transformation to ingest it into `main` yourself, on either LanceDB OSS or Enterprise. See our documentation on [branches](/tables/branching) for more information. # SDKs and REST API Reference Source: https://docs.lancedb.com/api-reference/index SDK and REST API reference for LanceDB Enterprise and OSS. For detailed information of the available functions and methods in your preferred language's SDKs, refer to the API documentation linked below. If you're looking for a REST API reference, visit the [REST API](/api-reference/rest) page. If you're looking for conceptual and practical namespace guidance before diving into method signatures, see [Namespaces and Catalog Model](/namespaces) and [Using Namespaces in SDKs](/namespaces/usage). ## Supported SDKs Python, Typescript and Rust SDKs are officially supported by LanceDB. You can use these SDKs to interact with both LanceDB OSS and Enterprise deployments. | Reference | Description | | :-------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | [Python](https://lancedb.github.io/lancedb/python/python/) | Full-featured Python client with pandas & numpy integration | | [Typescript](https://lancedb.github.io/lancedb/js/) | A TypeScript wrapper around the Rust library, built with `napi-rs` | | [Rust](https://docs.rs/lancedb/latest/lancedb/index.html) | Native Rust library with persistent-storage and high performance | ## REST API SDKs Enterprise REST API-based SDKs provide a convenient way to interact with LanceDB Enterprise deployments using the Lance REST Namespace API. | Reference | Description | | :------------------------------------------------------------------------ | ------------------------------- | | [Java](https://lancedb.github.io/lancedb/java/java/) | REST API Enterprise SDK in Java | ## Community-driven SDKs In addition to the officially supported SDKs, the LanceDB community may contribute SDKs in other languages. These SDKs may not have the same level of support or feature parity as the official ones supported by LanceDB, but they can be an option for users working in languages other than those listed above. | Reference | Description | | :---------------------------------------------------------------------------------------- | -------------------------------------------------- | | [Go](https://pkg.go.dev/github.com/lancedb/lancedb-go/pkg/lancedb) | Community-contributed Go SDK for LanceDB | | [Ruby](https://github.com/scientist-labs/lancelot) | Community-contributed Ruby bindings for LanceDB | | [Swift](https://github.com/RyanLisse/LanceDbSwiftKit) | Community-contributed Swift SDK for LanceDB | | [R](https://github.com/CathalByrneGit/lancedb) | Community-contributed R package for LanceDB | | [Flutter](https://github.com/Alexcn/flutter_lancedb) | Community-contributed Flutter bindings for LanceDB | # REST API Reference Source: https://docs.lancedb.com/api-reference/rest/index API reference for LanceDB [Lance REST Namespace](https://lance.org/format/namespace/) spec is an OpenAPI protocol that enables reading, writing and managing Lance tables by connecting those metadata services or building a custom metadata server in a standardized way. LanceDB OSS allows you to interface with Lance tables via the REST Namespace. LanceDB Enterprise provides an extended REST API with additional endpoints for managing tables and data. If you have specific needs or questions about the Enterprise REST API Namespace, please [contact us](mailto:support@lancedb.com). ## Authentication Enterprise All HTTP requests to LanceDB APIs must contain an x-api-key header that specifies a valid API key and must be encoded as JSON or Arrow RPC. To authenticate to the Enterprise REST API, you need the endpoint for your deployment and a valid API key for that deployment. ### Get your Enterprise credentials 1. Obtain the following values from your LanceDB administrator or the LanceDB team that provisioned your Enterprise deployment: * an API key * your Enterprise REST endpoint * your database name, if your deployment uses a private endpoint or `host_override` 2. Export those values in your terminal: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} export LANCEDB_API_KEY="" export LANCEDB_URI="https://your-enterprise-endpoint.com" export LANCEDB_DATABASE="your-database-name" ``` 3. If your Enterprise deployment is private, connect through the private network endpoint provided for your deployment. For example, Azure Private Link deployments commonly use a private IP or an internal DNS name as the endpoint. ### Verify authentication 4. Check that you can reach the deployment and list tables: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl -X GET "$LANCEDB_URI/v1/tables" \ -H "Content-Type: application/json" \ -H "x-api-key: $LANCEDB_API_KEY" \ -H "x-lancedb-database: $LANCEDB_DATABASE" ``` If your deployment endpoint already includes the database host name, you can omit the `x-lancedb-database` header. 5. Create a table to confirm write access. Let's call it `words`. ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl -X POST "$LANCEDB_URI/v1/tables/words" \ -H "Content-Type: application/vnd.apache.arrow.stream" \ -H "x-api-key: $LANCEDB_API_KEY" \ -H "x-lancedb-database: $LANCEDB_DATABASE" ``` 6. Check that the table has been created: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl -X GET "$LANCEDB_URI/v1/tables" \ -H "Content-Type: application/json" \ -H "x-api-key: $LANCEDB_API_KEY" \ -H "x-lancedb-database: $LANCEDB_DATABASE" ``` That's it -- you're connected! Now, you can start adding data and querying it. You can visit the tutorial section to build your own applications with LanceDB. Check out our tutorials on building various applications with LanceDB. # Create a materialized view Source: https://docs.lancedb.com/api-reference/rest/materializedview/create-a-materialized-view /api-reference/rest/openapi.yml post /v1/materialized_view/{id}/create Create a materialized view at identifier `id`. The view may be query-backed, UDTF-backed, or chunker-backed, controlled by the `kind` discriminator. # Trigger an async materialized view refresh Source: https://docs.lancedb.com/api-reference/rest/materializedview/trigger-an-async-materialized-view-refresh /api-reference/rest/openapi.yml post /v1/materialized_view/{id}/refresh Trigger an asynchronous refresh job for materialized view `id`. Returns a job ID for tracking. # Check if a namespace exists Source: https://docs.lancedb.com/api-reference/rest/namespace/check-if-a-namespace-exists /api-reference/rest/openapi.yml post /v1/namespace/{id}/exists Check if namespace `id` exists. This operation must behave exactly like the DescribeNamespace API, except it does not contain a response body. REST NAMESPACE ONLY REST namespace conveys the result through the HTTP status code with no response body. The REST response maps to the `NamespaceExistsResponse` model as follows: - a `200` response means the namespace exists; a `404` response means it does not - response headers map to `context` via the `header.` prefix (see the `Context` schema) # Create a new namespace Source: https://docs.lancedb.com/api-reference/rest/namespace/create-a-new-namespace /api-reference/rest/openapi.yml post /v1/namespace/{id}/create Create new namespace `id`. During the creation process, the implementation may modify user-provided `properties`, such as adding additional properties like `created_at` to user-provided properties, omitting any specific property, or performing actions based on any property value. # Describe a namespace Source: https://docs.lancedb.com/api-reference/rest/namespace/describe-a-namespace /api-reference/rest/openapi.yml post /v1/namespace/{id}/describe Describe the detailed information for namespace `id`. # Drop a namespace Source: https://docs.lancedb.com/api-reference/rest/namespace/drop-a-namespace /api-reference/rest/openapi.yml post /v1/namespace/{id}/drop Drop namespace `id` from its parent namespace. # List namespaces Source: https://docs.lancedb.com/api-reference/rest/namespace/list-namespaces /api-reference/rest/openapi.yml get /v1/namespace/{id}/list List all child namespace names of the parent namespace `id`. REST NAMESPACE ONLY REST namespace uses GET to perform this operation without a request body. It passes in the `ListNamespacesRequest` information in the following way: - `id`: pass through path parameter of the same name - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name # List tables in a namespace Source: https://docs.lancedb.com/api-reference/rest/namespace/list-tables-in-a-namespace /api-reference/rest/openapi.yml get /v1/namespace/{id}/table/list List all child table names of the parent namespace `id`. REST NAMESPACE ONLY REST namespace uses GET to perform this operation without a request body. It passes in the `ListTablesRequest` information in the following way: - `id`: pass through path parameter of the same name - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name - `include_declared`: pass through query parameter of the same name # Add new columns to table schema Source: https://docs.lancedb.com/api-reference/rest/table/add-new-columns-to-table-schema /api-reference/rest/openapi.yml post /v1/table/{id}/add_columns Add new columns to table `id` using SQL expressions or default values. # Analyze query execution plan Source: https://docs.lancedb.com/api-reference/rest/table/analyze-query-execution-plan /api-reference/rest/openapi.yml post /v1/table/{id}/analyze_plan Analyze the query execution plan for a query against table `id`. Returns detailed statistics and analysis of the query execution plan. REST NAMESPACE ONLY REST namespace returns the response as a plain string instead of the `AnalyzeTableQueryPlanResponse` JSON object. # Atomically commit a batch of mixed table operations Source: https://docs.lancedb.com/api-reference/rest/table/atomically-commit-a-batch-of-mixed-table-operations /api-reference/rest/openapi.yml post /v1/table/batch-commit Atomically commit a batch of table operations. This is a generalized version of `BatchCreateTableVersions` that supports mixed operation types within a single atomic transaction at the metadata layer. Supported operation types: - `DeclareTable`: Declare (reserve) a new table - `CreateTableVersion`: Create a new version entry for a table - `DeleteTableVersions`: Delete version ranges from a table - `DeregisterTable`: Deregister (soft-delete) a table All operations are committed atomically: either all succeed or none are applied. # Atomically create versions for multiple tables Source: https://docs.lancedb.com/api-reference/rest/table/atomically-create-versions-for-multiple-tables /api-reference/rest/openapi.yml post /v1/table/version/batch-create Atomically create new version entries for multiple tables. This operation is atomic: either all table versions are created successfully, or none are created. If any version creation fails (e.g., due to conflict), the entire batch operation fails. Each entry in the request specifies the table identifier and version details. This supports `put_if_not_exists` semantics for each version entry. # Check if a table exists Source: https://docs.lancedb.com/api-reference/rest/table/check-if-a-table-exists /api-reference/rest/openapi.yml post /v1/table/{id}/exists Check if table `id` exists. This operation should behave exactly like DescribeTable, except it does not contain a response body. REST NAMESPACE ONLY REST namespace conveys the result through the HTTP status code with no response body. The REST response maps to the `TableExistsResponse` model as follows: - a `200` response means the table exists; a `404` response means it does not - response headers map to `context` via the `header.` prefix (see the `Context` schema) For DirectoryNamespace implementation, a table exists if either: - The table has Lance data versions (regular table created with CreateTable) - A `.lance-reserved` file exists in the table directory (declared table created with DeclareTable) # Count rows in a table Source: https://docs.lancedb.com/api-reference/rest/table/count-rows-in-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/count_rows Count the number of rows in table `id` REST NAMESPACE ONLY REST namespace returns the response as a plain integer instead of the `CountTableRowsResponse` JSON object. The REST response maps to the `CountTableRowsResponse` model as follows: - the integer response body maps to `count` - response headers map to `context` via the `header.` prefix (see the `Context` schema) # Create a new branch Source: https://docs.lancedb.com/api-reference/rest/table/create-a-new-branch /api-reference/rest/openapi.yml post /v1/table/{id}/branches/create Create a new branch for table `id` starting from a source ref (another branch and/or version), defaulting to the latest version of the main branch. # Create a new table version Source: https://docs.lancedb.com/api-reference/rest/table/create-a-new-table-version /api-reference/rest/openapi.yml post /v1/table/{id}/version/create Create a new version entry for table `id`. This operation supports `put_if_not_exists` semantics. The operation will fail with 409 Conflict if the version already exists. # Create a new tag Source: https://docs.lancedb.com/api-reference/rest/table/create-a-new-tag /api-reference/rest/openapi.yml post /v1/table/{id}/tags/create Create a new tag for table `id` that points to a specific version. # Create a scalar index on a table Source: https://docs.lancedb.com/api-reference/rest/table/create-a-scalar-index-on-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/create_scalar_index Create a scalar index on a table field for faster filtering operations. Supports scalar indexes (BTREE, BITMAP, LABEL_LIST, FTS, etc.). This is an alias for CreateTableIndex specifically for scalar indexes. Index creation is handled asynchronously. Use the `ListTableIndices` and `DescribeTableIndexStats` operations to monitor index creation progress. # Create a table with the given name Source: https://docs.lancedb.com/api-reference/rest/table/create-a-table-with-the-given-name /api-reference/rest/openapi.yml post /v1/table/{id}/create Create table `id` in the namespace with the given data in Arrow IPC stream. The schema of the Arrow IPC stream is used as the table schema. If the stream is empty, the API creates a new empty table. REST NAMESPACE ONLY REST namespace uses Arrow IPC stream as the request body. It passes in the `CreateTableRequest` information in the following way: - `id`: pass through path parameter of the same name - `mode`: pass through query parameter of the same name - `properties`: serialize as a single JSON-encoded query parameter such as `properties={"user":"alice","team":"eng"}`; these are business logic properties managed by the namespace implementation outside Lance context - `storage_options`: serialize as a single JSON-encoded query parameter such as `storage_options={"aws_region":"us-east-1","timeout":"30s"}`; these configure write-time overrides for data and metadata written during table creation # Create an index on a table Source: https://docs.lancedb.com/api-reference/rest/table/create-an-index-on-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/create_index Create an index on a table field for faster search operations. Supports vector indexes (IVF_FLAT, IVF_HNSW_SQ, IVF_PQ, etc.) and scalar indexes (BTREE, BITMAP, FTS, etc.). Index creation is handled asynchronously. Use the `ListTableIndices` and `DescribeTableIndexStats` operations to monitor index creation progress. # Declare a table Source: https://docs.lancedb.com/api-reference/rest/table/declare-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/declare Declare a table with the given name without touching storage. This is a metadata-only operation that records the table existence and sets up aspects like access control. For DirectoryNamespace implementation, this creates a `.lance-reserved` file in the table directory to mark the table's existence without creating actual Lance data files. # Delete a branch Source: https://docs.lancedb.com/api-reference/rest/table/delete-a-branch /api-reference/rest/openapi.yml post /v1/table/{id}/branches/delete Delete an existing branch from table `id`. # Delete a tag Source: https://docs.lancedb.com/api-reference/rest/table/delete-a-tag /api-reference/rest/openapi.yml post /v1/table/{id}/tags/delete Delete an existing tag from table `id`. # Delete rows from a table Source: https://docs.lancedb.com/api-reference/rest/table/delete-rows-from-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/delete Delete rows from table `id`. # Delete table version records Source: https://docs.lancedb.com/api-reference/rest/table/delete-table-version-records /api-reference/rest/openapi.yml post /v1/table/{id}/version/delete Delete version metadata records for table `id`. This operation deletes version tracking records, NOT the actual table data. It supports deleting ranges of versions for efficient bulk cleanup. Special range values: - `start_version: 0` with `end_version: -1` means delete ALL version records # Deregister a table Source: https://docs.lancedb.com/api-reference/rest/table/deregister-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/deregister Deregister table `id` from its namespace. # Describe a specific table version Source: https://docs.lancedb.com/api-reference/rest/table/describe-a-specific-table-version /api-reference/rest/openapi.yml post /v1/table/{id}/version/describe Describe the detailed information for a specific version of table `id`. Returns the manifest path and metadata for the specified version. # Describe information of a table Source: https://docs.lancedb.com/api-reference/rest/table/describe-information-of-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/describe Describe the detailed information for table `id`. REST NAMESPACE ONLY REST namespace passes `with_table_uri`, `load_detailed_metadata`, and `check_declared` as query parameters instead of in the request body. # Drop a specific index Source: https://docs.lancedb.com/api-reference/rest/table/drop-a-specific-index /api-reference/rest/openapi.yml post /v1/table/{id}/index/{index_name}/drop Drop the specified index from table `id`. REST NAMESPACE ONLY REST namespace does not use a request body for this operation. The `DropTableIndexRequest` information is passed in the following way: - `id`: pass through path parameter of the same name - `index_name`: pass through path parameter of the same name # Drop a table Source: https://docs.lancedb.com/api-reference/rest/table/drop-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/drop Drop table `id` and delete its data. REST NAMESPACE ONLY REST namespace does not use a request body for this operation. The `DropTableRequest` information is passed in the following way: - `id`: pass through path parameter of the same name # Get query execution plan explanation Source: https://docs.lancedb.com/api-reference/rest/table/get-query-execution-plan-explanation /api-reference/rest/openapi.yml post /v1/table/{id}/explain_plan Get the query execution plan for a query against table `id`. Returns a human-readable explanation of how the query will be executed. REST NAMESPACE ONLY REST namespace returns the response as a plain string instead of the `ExplainTableQueryPlanResponse` JSON object. # Get table index statistics Source: https://docs.lancedb.com/api-reference/rest/table/get-table-index-statistics /api-reference/rest/openapi.yml post /v1/table/{id}/index/{index_name}/stats Get statistics for a specific index on a table. Returns information about the index type, distance type (for vector indices), and row counts. # Get table statistics Source: https://docs.lancedb.com/api-reference/rest/table/get-table-statistics /api-reference/rest/openapi.yml post /v1/table/{id}/stats Get statistics for table `id`, including row counts, data sizes, and column statistics. # Get version for a specific tag Source: https://docs.lancedb.com/api-reference/rest/table/get-version-for-a-specific-tag /api-reference/rest/openapi.yml post /v1/table/{id}/tags/version Get the version number that a specific tag points to for table `id`. # Insert records into a table Source: https://docs.lancedb.com/api-reference/rest/table/insert-records-into-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/insert Insert new records into table `id`. For tables that have been declared but not yet created on storage (is_only_declared=true), this operation will create the table with the provided data. REST NAMESPACE ONLY REST namespace uses Arrow IPC stream as the request body. It passes in the `InsertIntoTableRequest` information in the following way: - `id`: pass through path parameter of the same name - `mode`: pass through query parameter of the same name # List all branches for a table Source: https://docs.lancedb.com/api-reference/rest/table/list-all-branches-for-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/branches/list List all branches that have been created for table `id`. Returns a map of branch names to their contents. REST NAMESPACE ONLY REST namespace does not use a request body for this operation. The `ListTableBranchesRequest` information is passed in the following way: - `id`: pass through path parameter of the same name - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name # List all tables Source: https://docs.lancedb.com/api-reference/rest/table/list-all-tables /api-reference/rest/openapi.yml get /v1/table List all tables across all namespaces. REST NAMESPACE ONLY REST namespace uses GET to perform this operation without a request body. It passes in the `ListAllTablesRequest` information in the following way: - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name - `delimiter`: pass through query parameter of the same name - `include_declared`: pass through query parameter of the same name # List all tags for a table Source: https://docs.lancedb.com/api-reference/rest/table/list-all-tags-for-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/tags/list List all tags that have been created for table `id`. Returns a map of tag names to their corresponding version numbers and metadata. REST NAMESPACE ONLY REST namespace does not use a request body for this operation. The `ListTableTagsRequest` information is passed in the following way: - `id`: pass through path parameter of the same name - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name # List all versions of a table Source: https://docs.lancedb.com/api-reference/rest/table/list-all-versions-of-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/version/list List all versions (commits) of table `id` with their metadata. Use `descending=true` to guarantee versions are returned in descending order (latest to oldest). Otherwise, the ordering is implementation-defined. REST NAMESPACE ONLY REST namespace does not use a request body for this operation. The `ListTableVersionsRequest` information is passed in the following way: - `id`: pass through path parameter of the same name - `page_token`: pass through query parameter of the same name - `limit`: pass through query parameter of the same name - `descending`: pass through query parameter of the same name # List indexes on a table Source: https://docs.lancedb.com/api-reference/rest/table/list-indexes-on-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/index/list List all indices created on a table. Returns information about each index including name, columns, status, and UUID. # Merge insert (upsert) records into a table Source: https://docs.lancedb.com/api-reference/rest/table/merge-insert-upsert-records-into-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/merge_insert Performs a merge insert (upsert) operation on table `id`. This operation updates existing rows based on a matching column and inserts new rows that don't match. It returns the number of rows inserted and updated. For tables that have been declared but not yet created on storage (is_only_declared=true), this operation will create the table with the provided data (since there are no existing rows to merge with). REST NAMESPACE ONLY REST namespace uses Arrow IPC stream as the request body. It passes in the `MergeInsertIntoTableRequest` information in the following way: - `id`: pass through path parameter of the same name - `on`: pass through query parameter of the same name - `when_matched_update_all`: pass through query parameter of the same name - `when_matched_update_all_filt`: pass through query parameter of the same name - `when_not_matched_insert_all`: pass through query parameter of the same name - `when_not_matched_by_source_delete`: pass through query parameter of the same name - `when_not_matched_by_source_delete_filt`: pass through query parameter of the same name # Modify existing columns Source: https://docs.lancedb.com/api-reference/rest/table/modify-existing-columns /api-reference/rest/openapi.yml post /v1/table/{id}/alter_columns Modify existing columns in table `id`, such as renaming or changing data types. # Query a table Source: https://docs.lancedb.com/api-reference/rest/table/query-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/query Query table `id` with vector search, full text search and optional SQL filtering. Returns results in Arrow IPC file or stream format. REST NAMESPACE ONLY REST namespace returns the response as Arrow IPC file binary data instead of the `QueryTableResponse` JSON object. The REST response maps to the `QueryTableResponse` model as follows: - the Arrow IPC file binary body maps to `data` - response headers map to `context` via the `header.` prefix (see the `Context` schema) # Register a table to a namespace Source: https://docs.lancedb.com/api-reference/rest/table/register-a-table-to-a-namespace /api-reference/rest/openapi.yml post /v1/table/{id}/register Register an existing table at a given storage location as `id`. # Remove columns from table Source: https://docs.lancedb.com/api-reference/rest/table/remove-columns-from-table /api-reference/rest/openapi.yml post /v1/table/{id}/drop_columns Remove specified columns from table `id`. # Rename a table Source: https://docs.lancedb.com/api-reference/rest/table/rename-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/rename Rename table `id` to a new name. # Restore table to a specific version Source: https://docs.lancedb.com/api-reference/rest/table/restore-table-to-a-specific-version /api-reference/rest/openapi.yml post /v1/table/{id}/restore Restore table `id` to a specific version. # Trigger an async column backfill job Source: https://docs.lancedb.com/api-reference/rest/table/trigger-an-async-column-backfill-job /api-reference/rest/openapi.yml post /v1/table/{id}/backfill_column Trigger an asynchronous backfill job for a computed column on table `id`. The column must be a virtual (UDF-backed) column. Returns a job ID for tracking. # Update a tag to point to a different version Source: https://docs.lancedb.com/api-reference/rest/table/update-a-tag-to-point-to-a-different-version /api-reference/rest/openapi.yml post /v1/table/{id}/tags/update Update an existing tag for table `id` to point to a different version. # Update per-field metadata Source: https://docs.lancedb.com/api-reference/rest/table/update-per-field-metadata /api-reference/rest/openapi.yml post /v1/table/{id}/update_field_metadata Update the Arrow field (column) metadata for table `id`. Each entry targets a field by `path` and merges the provided key-value pairs into that field's existing metadata, or replaces it when `replace` is true. A null metadata value deletes that key. # Update rows in a table Source: https://docs.lancedb.com/api-reference/rest/table/update-rows-in-a-table /api-reference/rest/openapi.yml post /v1/table/{id}/update Update existing rows in table `id`. # Update table schema metadata Source: https://docs.lancedb.com/api-reference/rest/table/update-table-schema-metadata /api-reference/rest/openapi.yml post /v1/table/{id}/schema_metadata/update Replace the schema metadata for table `id` with the provided key-value pairs. REST NAMESPACE ONLY REST namespace uses a direct object (map of string to string) as both request and response body instead of the wrapped `UpdateTableSchemaMetadataRequest` and `UpdateTableSchemaMetadataResponse`. # Alter information of a transaction. Source: https://docs.lancedb.com/api-reference/rest/transaction/alter-information-of-a-transaction /api-reference/rest/openapi.yml post /v1/transaction/{id}/alter Alter a transaction with a list of actions such as setting status or properties. The server should either succeed and apply all actions, or fail and apply no action. # Describe information about a transaction Source: https://docs.lancedb.com/api-reference/rest/transaction/describe-information-about-a-transaction /api-reference/rest/openapi.yml post /v1/transaction/{id}/describe Return a detailed information for a given transaction # Tutorial: Use the LanceDB agent skill Source: https://docs.lancedb.com/build-with-ai-agents Install the LanceDB skill and use an AI coding agent to quickly build a multimodal ingestion pipeline. The LanceDB agent skill gives coding agents a maintained reference for the Python and TypeScript APIs. It also covers portable OSS and Enterprise code, ingestion performance, and branch operations. Install it in your project: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Install the lancedb skill npx skills add lancedb/lancedb # Update the skill npx skills update lancedb ``` The skill supplements the agent's training data with current LanceDB instructions. The installer downloads the [`lancedb` skills plugin](https://github.com/lancedb/lancedb/tree/main/plugins/lancedb) and makes it available to the agents you select. The installation directory depends on the agent client. Universal agents typically use `.agents/skills`. The installer also creates or updates `skills-lock.json`. This file records where the skill came from, its path in the source repository, and a hash of the installed content. ```json theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} { "version": 1, "skills": { "lancedb": { "source": "lancedb/lancedb", "sourceType": "github", "skillPath": "plugins/lancedb/skills/lancedb/SKILL.md", "computedHash": "..." } } } ``` Commit the lockfile if you want skill updates to go through code review. Before committing a new hash, inspect the changes to `SKILL.md` and its reference files. The repository history will then show which snapshot each revision used. ## Get started with the LanceDB agent skill This tutorial uses the Camelot dataset from the [quickstart](/quickstart), with a portrait added for each character. Each LanceDB row contains validated metadata and raw JPEG bytes. Text, images, and any embeddings you add later remain in the same table. ### 1. Download the multimodal dataset From a new project directory, download the JSON file and portraits: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} mkdir -p data/img BASE_URL="https://docs.lancedb.com/static/assets/tutorials/build-with-ai-agents/camelot/data" curl -fsSL "$BASE_URL/camelot.json" -o data/camelot.json for image in \ arthur.jpg \ guinevere.jpg \ merlin.jpg \ mordred.jpg \ sir_galahad.jpg \ sir_gawain.jpg \ sir_lancelot.jpg \ sir_percival.jpg do curl -fsSL "$BASE_URL/img/$image" -o "data/img/$image" done ``` Each JSON record has this shape: ```json theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} { "id": 2, "name": "Merlin", "role": "Wizard and Advisor", "description": "A powerful wizard and prophet who mentors Arthur.", "stats": { "strength": 2, "courage": 4, "magic": 5, "wisdom": 5 }, "img": "data/img/merlin.jpg" } ``` JSON input may have missing fields, unexpected fields, or values of the wrong type. The LanceDB skill tells the agent to validate each record with strict Pydantic models before writing it. After the agent writes the pipeline, inspect the schema, batching, and write path rather than assuming it followed the skill correctly. ### 2. Prompt your agent to build the pipeline Install the Python packages used by the example: ```bash icon=terminal theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} uv init uv add lancedb pyarrow pydantic ``` If you're using LanceDB Enterprise, ask the agent to ingest into an Enterprise table, provide the relevant environment variables for connecting to your Enterprise deployment in a local `.env` file, and point the agent to it. ```text .env theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} LANCEDB_URI=db://your_project_name LANCEDB_API_KEY=your_api_key_here LANCEDB_REGION=us-east-1 LANCEDB_HOST_OVERRIDE=https://hostname@ip_address ``` If you're using LanceDB OSS, no connection settings are required, as it runs as an embedded retrieval library. A simple prompt like this should work: ```text Agent prompt theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # If using OSS Use the lancedb skill to ingest the dataset in `data/` into a LanceDB OSS table. # If using enterprise Use the lancedb skill to ingest the dataset in `data/` into a LanceDB Enterprise table using the connection information in `.env`. ``` Agents that scan `.agents/skills` should find the skill automatically. If your agent does not find it automatically, simply ask it to use the `lancedb` skill in the prompt. That should be enough! The agent will create `ingest_multimodal.py`, or similar. The following sections inspect the script to verify that it follows the skill's guidance. #### Data validation The skill encourages the agent to validate each record with Pydantic before writing it. The agent should ideally define a schema for the table and a nested schema for the `stats` field. In this case, our agent correctly defined `Character` and `Stats` Pydantic models and validated the JSON before adding it to the table. #### Batched ingestion Naively calling `table.add()` once per row is slow, and is considered an anti-pattern in LanceDB. The skill encourages the agent to collect incoming rows into batches and write them with a single `table.add()` call. When you use the skill, the agent should produce something like this: The script calls `Character.model_validate(...)` before adding a record to the batch. If validation fails, that batch is never written. The function yields up to `batch_size` rows at a time, providing an iterable of batches for the ingestion step, shown next. #### Table maintenance For LanceDB OSS, the skill instructs the agent to call `table.optimize()` after the ingestion loop. This compacts small fragments, cleans up old versions according to the retention policy, and incorporates new data into indexes. If you're using LanceDB Enterprise, the skill mentions that this step is not needed because LanceDB Enterprise handles maintenance automatically. This example dataset has only eight rows, so the default batch size writes it in one call. Larger inputs should still avoid single-row write commits. ### 3. Run the OSS pipeline ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} uv run python ingest_multimodal.py ``` The table now contains the validated character records and their JPEG bytes. Here are the first three rows: | Image | Character | Role | | ----------------------------- | --------------- | ------------------ | | King Arthur | King Arthur | King of Camelot | | Merlin | Merlin | Wizard and Advisor | | Queen Guinevere | Queen Guinevere | Queen of Camelot | Once your pipeline works, you can [run experiments on branches](/agent-branch-experiments) to try new embedding models, parsers, or search settings without touching `main`. ## Takeaways The example in this tutorial was small, but similar ideas apply to other workflows, too. Give the agent the data source, the constraints it must respect, and the artifacts it should return. The skill supplies LanceDB-specific guidance, but it's the user's responsibility to ensure the output makes sense for the application. ### Try the skill with your own dataset The skills shown in this tutorial should generalize reasonably well to other use cases. If you find any issues, open [an issue](https://github.com/lancedb/lancedb/issues) on GitHub, clearly describing the intended behavior. You can choose OSS or Enterprise based on how the work will run: Start with [LanceDB OSS](/quickstart) during the early stages of a project when an agent is helping you prototype, explore a dataset, or run small workflows on a subset of the data. The application owns the storage and lifecycle work, so ask the agent to validate inputs, write in batches, and it will include table maintenance operations such as `optimize()` where appropriate. Choose [LanceDB Enterprise](/enterprise) when the resulting table becomes shared production infrastructure, and the workload needs distributed capacity, private deployment, or platform-managed operations. The underlying data format and table API stay the same, so the pipeline does not need to be redesigned. The agent instead connects to a remote `db://` table and lets the cluster handle maintenance and background work. # ADE20K Source: https://docs.lancedb.com/datasets/ade20k A Lance-formatted version of the full ADE20K scene parsing benchmark, sourced from 1aurent/ADE20K. Each row is one scene image with its inline JPEG bytes, a per-pixel semantic segmentation map encoded as PNG bytes, an optional instance map, scene… Source dataset card and downloadable files for `lance-format/ade20k-lance`. A Lance-formatted version of the full [ADE20K scene parsing benchmark](https://groups.csail.mit.edu/vision/datasets/ADE20K/), sourced from [`1aurent/ADE20K`](https://huggingface.co/datasets/1aurent/ADE20K). Each row is one scene image with its inline JPEG bytes, a per-pixel semantic segmentation map encoded as PNG bytes, an optional instance map, scene class labels, the full per-polygon object-name list, an OpenCLIP image embedding, and pre-built indices — all available directly from the Hub at `hf://datasets/lance-format/ade20k-lance/data`. ## Key features * **Inline image and segmentation bytes** — both the JPEG image and the RGB-encoded PNG segmentation map ride on the same row, so an annotated example is a single row read with no sidecar files. * **Per-polygon object metadata** — `object_names` keeps the full list (one entry per annotated polygon), `objects_present` is the deduped set used for class-presence filters, and `num_objects` is precomputed. * **CLIP image embeddings** (`image_emb`, OpenCLIP ViT-B/32, 512-d, cosine-normalized) for visual retrieval over scenes. * **Indices shipped on disk** — `IVF_PQ` on `image_emb`, `BTREE` on `num_objects`, and `LABEL_LIST` on `objects_present` for fast `array_has_any` / `array_has_all` predicates. ## Splits | Split | Rows | | ------------------ | ------ | | `train.lance` | 25,574 | | `validation.lance` | 2,000 | ## Schema | Column | Type | Notes | | ----------------- | ------------------------------- | --------------------------------------------------------------------------- | | `id` | `int64` | Row index within split | | `image` | `large_binary` | Inline JPEG bytes | | `segmentation` | `large_binary` | Inline PNG bytes — semantic segmentation map (RGB encoding per ADE20K spec) | | `instance` | `large_binary?` | Inline PNG bytes — instance map; null if not provided | | `filename` | `string` | ADE20K relative filename | | `scene` | `list` | Scene class labels (e.g. `["bathroom"]`) | | `object_names` | `list` | Per-polygon object names (one entry per polygon, not deduped) | | `objects_present` | `list` | Deduped object names — feeds the `LABEL_LIST` index | | `num_objects` | `int32` | Number of annotated objects | | `image_emb` | `fixed_size_list` | OpenCLIP ViT-B/32 image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `num_objects` — fast range filters on scene complexity * `LABEL_LIST` on `objects_present` — supports `array_has_any` / `array_has_all` for class-presence filtering ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/ade20k-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["filename"], row["scene"], row["num_objects"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/ade20k-lance/data/validation.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, ANN search, and any mutation are far faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/ade20k-lance --repo-type dataset --local-dir ./ade20k-lance > ``` > > Then point Lance or LanceDB at `./ade20k-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes approximate-nearest-neighbor scene retrieval a single call. In production you would encode a query image through the same OpenCLIP ViT-B/32 model used at ingest and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding stored on row 42 as a runnable stand-in, so the snippet works without loading any model. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") tbl = db.open_table("validation") seed = ( tbl.search() .select(["image_emb", "filename", "scene"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["filename", "scene", "objects_present"]) .limit(10) .to_list() ) print("query scene:", seed["scene"]) for r in hits: print(f" {r['filename']} scene={r['scene']} objs={r['objects_present'][:5]}") ``` Because the embeddings are cosine-normalized, the first hit will typically be the source image itself — a useful sanity check. Tune `nprobes` and `refine_factor` to trade recall against latency for your workload. ## Curate Curation for a semantic-segmentation workflow usually means picking scenes that contain specific classes, possibly bounded by complexity. The `LABEL_LIST` index on `objects_present` makes class-presence predicates trivial, and Lance evaluates them inside the same scan as a structural filter on `num_objects`. The bounded `.limit(500)` keeps the result small and inspectable, and the `segmentation` blob is left out of the projection so the candidate scan is dominated by metadata, not PNG bytes. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where( "array_has_all(objects_present, ['bed', 'window']) AND num_objects >= 8", prefilter=True, ) .select(["id", "filename", "scene", "objects_present", "num_objects"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first scene: {candidates[0]['scene']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. Swapping `array_has_all` for `array_has_any` widens the recall; replacing the structural predicate with `num_objects BETWEEN 3 AND 6` selects simpler scenes for an ablation slice. ## 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 `has_person` flag and a `scene_label` string pulled out of the `scene` list, 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 corpus first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./ade20k-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "has_person": "array_has_any(objects_present, ['person'])", "scene_label": "element_at(scene, 1)", "complexity_bucket": "CASE WHEN num_objects < 5 THEN 'sparse' " "WHEN num_objects < 15 THEN 'medium' ELSE 'dense' END", }) ``` If the values you want to attach already live in another table (offline panoptic ids, predictions from a baseline segmenter, a second-pass embedding), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "baseline_miou": pa.array([0.41, 0.55, 0.62]), }) tbl.merge(predictions, 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., re-running a segmentation model over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 semantic-segmentation run, project the JPEG bytes and the segmentation PNG bytes; both are decoded inside the training step. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "segmentation"]) loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=4) for batch in loader: # batch carries only the JPEG and PNG byte columns; decode both, # remap the ADE20K RGB-encoded mask to class ids, forward, loss... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "objects_present"]` to `select_columns(...)` on the next run skips JPEG and PNG decoding entirely and reads only the cached 512-d vectors plus the deduped class list, which is the right shape for training a lightweight scene classifier or a class-presence probe on top of frozen 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./ade20k-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("segmenter-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="segmenter-baseline-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. A serving pipeline locked to `segmenter-baseline-v1` keeps reading the exact same segmentation maps and class lists while the dataset evolves in parallel; newly merged predictions or evolved columns 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, so changes in mIoU 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/ade20k-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("array_has_any(objects_present, ['bed', 'sofa', 'chair']) AND num_objects >= 5") .select(["id", "image", "segmentation", "filename", "scene", "objects_present", "num_objects", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./ade20k-indoor-subset") local_db.create_table("train", batches) ``` The resulting `./ade20k-indoor-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/ade20k-lance/data` for `./ade20k-indoor-subset`. ## Source & license Converted from [`1aurent/ADE20K`](https://huggingface.co/datasets/1aurent/ADE20K). ADE20K is released under the [BSD 3-Clause license](https://ade20k.csail.mit.edu/terms/) by the MIT CSAIL Computer Vision group. ## Citation ``` @inproceedings{zhou2017scene, title={Scene Parsing through ADE20K Dataset}, author={Zhou, Bolei and Zhao, Hang and Puig, Xavier and Fidler, Sanja and Barriuso, Adela and Torralba, Antonio}, booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2017} } ``` # ChartQA Source: https://docs.lancedb.com/datasets/chartqa A Lance-formatted version of ChartQA, a benchmark for question answering over scientific and business charts that demands a mix of logical and visual reasoning, redistributed via lmms-lab/ChartQA. Each row carries the chart image as inline JPEG… Source dataset card and downloadable files for `lance-format/chartqa-lance`. A Lance-formatted version of [ChartQA](https://github.com/vis-nlp/ChartQA), a benchmark for question answering over scientific and business charts that demands a mix of logical and visual reasoning, redistributed via [`lmms-lab/ChartQA`](https://huggingface.co/datasets/lmms-lab/ChartQA). Each row carries the chart image as inline JPEG bytes, the natural-language question and reference answer(s), a question-type tag (`human` vs `augmented`), and paired CLIP embeddings for the image and the question — all available directly from the Hub at `hf://datasets/lance-format/chartqa-lance/data`. ## Key features * **Inline chart image bytes** in the `image` column — no sidecar files, no image folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `question_emb` (ViT-B/32, 512-dim, cosine-normalized) — so visual and textual retrieval are one indexed lookup. * **All reference answers preserved in `answers`** alongside a canonical `answer` string used for full-text search. * **Pre-built ANN, FTS, and scalar indices** covering both embedding columns, the question and answer strings, and the `type` tag. ## Splits | Split | Rows | Notes | | ------------ | ----- | ----------------------------------------- | | `test.lance` | 2,500 | Public test slice from `lmms-lab/ChartQA` | > The `lmms-lab/ChartQA` redistribution exposes the test split only. Train and validation live in the original ChartQA release; extend `chartqa/dataprep.py` with additional sources to add them. ## Schema | Column | Type | Notes | | -------------- | ------------------------------- | ------------------------------------------------ | | `id` | `int64` | Row index within split (natural join key) | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `string?` | Source does not assign explicit ids — null | | `question_id` | `string?` | Source does not assign explicit ids — null | | `question` | `string` | Natural-language question | | `answers` | `list` | Reference answer(s), typically a single string | | `answer` | `string` | First reference answer — canonical, used for FTS | | `type` | `string?` | Question type (`human` vs `augmented`) | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | | `question_emb` | `fixed_size_list` | CLIP text embedding of the question | ## Pre-built indices * `IVF_PQ` on `image_emb` — image-side vector search (cosine) * `IVF_PQ` on `question_emb` — text-side vector search (cosine) * `INVERTED` (FTS) on `question` and `answer` — keyword and hybrid search * `BITMAP` on `type` — fast filtering by question type ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/chartqa-lance", split="test", 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](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") tbl = db.open_table("test") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/chartqa-lance/data/test.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/chartqa-lance --repo-type dataset --local-dir ./chartqa-lance > ``` > > Then point Lance or LanceDB at `./chartqa-lance/data`. ## Search The bundled `IVF_PQ` index on `question_emb` makes question-to-question retrieval a single call: encode a query with the same CLIP model used at ingest (ViT-B/32, cosine-normalized) and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the `question_emb` already stored in row 42 as a runnable stand-in, so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") tbl = db.open_table("test") 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") .select(["question", "answer", "type"]) .limit(10) .to_list() ) print("query:", seed["question"]) for r in hits: print(f" [{r['type']}] {r['question'][:70]} -> {r['answer']}") ``` Swap `vector_column_name="question_emb"` for `image_emb` to do question-to-chart retrieval against the visual embedding instead — useful for finding charts whose layout is similar to a given prompt encoding. Because the dataset also ships an `INVERTED` index on `question` and `answer`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase like "percentage" or "highest bar" must literally appear in the question but you still want CLIP to do the heavy lifting on semantic similarity. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="question_emb") .vector(seed["question_emb"]) .text("percentage") .select(["question", "answer", "type"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" [{r['type']}] {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 combines a content predicate on the question text with a structural predicate on the question-type tag. 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. The example below collects human-authored questions that mention a percentage, which is a common slice for evaluating numeric-reasoning behaviour. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") tbl = db.open_table("test") candidates = ( tbl.search("percentage OR percent") .where("type = 'human'", prefilter=True) .select(["id", "question", "answer", "type"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['question'][:80]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of row 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 question and answer text rather than chart JPEGs. ## 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 `answer_length`, an `is_yes_no` flag, and an `is_numeric` 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 split first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./chartqa-lance/data") # local copy required for writes tbl = db.open_table("test") tbl.add_columns({ "answer_length": "length(answer)", "is_yes_no": "lower(answer) IN ('yes', 'no')", "is_numeric": "regexp_match(answer, '^-?[0-9]+(\\.[0-9]+)?%?$') IS NOT NULL", }) ``` If the values you want to attach already live in another table (model predictions on the test set, reasoning-chain annotations, a difficulty score), merge them in by joining on the `id` column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2]), "pred_answer": pa.array(["12%", "Yes", "34"]), "is_correct": pa.array([True, True, False]), }) tbl.merge(predictions, 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 chart-OCR model over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 fine-tuning a VLM on chart QA, project the chart bytes plus the question and answer; columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") tbl = db.open_table("test") train_ds = Permutation.identity(tbl).select_columns(["image", "question", "answer"]) loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; decode the JPEG bytes, # tokenize the question/answer pair, forward, backward... ... ``` 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 training a lightweight answer-classifier or a linear probe on top of frozen features. ## Versioning Every mutation to a Lance dataset, whether it adds a column, merges predictions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") tbl = db.open_table("test") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./chartqa-lance/data") local_tbl = local_db.open_table("test") local_tbl.tags.create("eval-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("test", version="eval-v1") tbl_v5 = db.open_table("test", version=5) ``` Pinning supports two workflows. An evaluation harness locked to `eval-v1` keeps producing comparable scores while the dataset evolves in parallel — newly added prediction columns 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 charts 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/chartqa-lance/data") remote_tbl = remote_db.open_table("test") batches = ( remote_tbl.search("percentage OR percent") .where("type = 'human'") .select(["id", "image", "question", "answer", "type", "image_emb", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./chartqa-human-subset") local_db.create_table("test", batches) ``` The resulting `./chartqa-human-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/chartqa-lance/data` for `./chartqa-human-subset`. ## Source & license Converted from [`lmms-lab/ChartQA`](https://huggingface.co/datasets/lmms-lab/ChartQA). The original ChartQA dataset is released under the GNU GPL-3.0 license by Masry et al. ## Citation ``` @inproceedings{masry2022chartqa, title={ChartQA: A Benchmark for Question Answering about Charts with Visual and Logical Reasoning}, author={Masry, Ahmed and Long, Do Xuan and Tan, Jia Qing and Joty, Shafiq and Hoque, Enamul}, booktitle={Findings of the Association for Computational Linguistics: ACL 2022}, year={2022} } ``` # CIFAR-10 Source: https://docs.lancedb.com/datasets/cifar10 A Lance-formatted version of CIFAR-10 covering 60,000 32×32 RGB images across ten balanced object classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image embedding, all backed… Source dataset card and downloadable files for `lance-format/cifar10-lance`. A Lance-formatted version of [CIFAR-10](https://huggingface.co/datasets/uoft-cs/cifar10) covering 60,000 32×32 RGB images across ten balanced object classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image embedding, all backed by a bundled `IVF_PQ` vector index plus scalar indices on the label columns and available directly from the Hub at `hf://datasets/lance-format/cifar10-lance/data`. ## Key features * **Inline PNG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (OpenCLIP `ViT-B-32` / `laion2b_s34b_b79k`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index. * **Scalar indices on both label columns** — `BTREE` on `label` and `BITMAP` on `label_name` — so class filters and class-conditioned search are constant-time lookups. * **One columnar dataset** — scan labels cheaply, then fetch image bytes only for the rows you want. ## Splits | Split | Rows | | ------------- | ------ | | `train.lance` | 50,000 | | `test.lance` | 10,000 | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------- | | `id` | `int64` | Row index within the split (natural join key for merges) | | `image` | `large_binary` | Inline PNG bytes (32×32 RGB) | | `label` | `int32` | Class id (0–9) | | `label_name` | `string` | One of `airplane`, `automobile`, `bird`, `cat`, `deer`, `dog`, `frog`, `horse`, `ship`, `truck` | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `label` — fast equality and range filters on the class id * `BITMAP` on `label_name` — fast filters across the ten class names ## 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 if your pipeline already speaks `Dataset` / `IterableDataset` or you want a quick streaming sample without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/cifar10-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label"], row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/cifar10-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/cifar10-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/cifar10-lance --repo-type dataset --local-dir ./cifar10-lance > ``` > > Then point Lance or LanceDB at `./cifar10-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` turns nearest-neighbor lookup on the 512-d CLIP space into a single call. In production you would encode a query image (or, for cross-modal text→image lookup, a tokenized prompt) through OpenCLIP `ViT-B-32` at runtime and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding already stored in row 42 as a runnable stand-in so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/cifar10-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "label_name"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label", "label_name"]) .limit(10) .to_list() ) print("query class:", seed["label_name"]) for r in hits: print(f" id={r['id']:>5} {r['label_name']}") ``` Because CIFAR-10 has only ten classes and the embeddings are cosine-normalized, near-neighbors of a seed image cluster tightly inside the seed's own class. Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency. ## Curate A typical curation pass for a classification workflow narrows the table to a single class (or a small set of confusable classes) before sampling. Because both label columns are indexed, the filter resolves without scanning the embedding or image bytes; the bounded `.limit(500)` keeps the output small enough to inspect or hand off as a manifest of row ids. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/cifar10-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where("label_name IN ('cat', 'dog')", prefilter=True) .select(["id", "label", "label_name"]) .limit(500) .to_list() ) print(f"{len(candidates)} cat/dog candidates") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `image_emb` columns are never read, so the network traffic for a 500-row candidate scan is dominated by the tiny label payload. ## 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_animal` flag and an `is_target_class` flag, 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 corpus first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./cifar10-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "is_animal": "label_name IN ('bird', 'cat', 'deer', 'dog', 'frog', 'horse')", "is_target_class": "label = 3", }) ``` If the values you want to attach already live in another table (offline labels from a stronger model, classifier predictions, per-row confidence scores), merge them in by joining on the `id` column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "pred_label": pa.array([3, 8, 0], type=pa.int32()), "pred_conf": pa.array([0.91, 0.74, 0.99]), }) tbl.merge(predictions, 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 second image encoder over the inline PNG bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/cifar10-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the PNG bytes, apply augmentations, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` to `select_columns(...)` on the next run skips PNG decoding entirely and reads only the cached 512-d vectors, which is the right shape for training a linear probe or a lightweight reranker on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/cifar10-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./cifar10-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 prediction columns or relabelings 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 labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/cifar10-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("label_name IN ('cat', 'dog')") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./cifar10-cats-dogs") local_db.create_table("train", batches) ``` The resulting `./cifar10-cats-dogs` 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/cifar10-lance/data` for `./cifar10-cats-dogs`. ## Source & license Converted from [`uoft-cs/cifar10`](https://huggingface.co/datasets/uoft-cs/cifar10). CIFAR-10 was collected by Alex Krizhevsky, Vinod Nair, and Geoffrey Hinton at the University of Toronto. ## Citation ``` @techreport{krizhevsky2009cifar10, title={Learning multiple layers of features from tiny images}, author={Krizhevsky, Alex and Hinton, Geoffrey}, year={2009}, institution={University of Toronto} } ``` # COCO Captions 2017 Source: https://docs.lancedb.com/datasets/coco-captions-2017 A Lance-formatted version of the COCO Captions 2017 corpus, redistributed via lmms-lab/COCO-Caption2017. Each row is one image with 5–7 human-written captions, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of… Source dataset card and downloadable files for `lance-format/coco-captions-2017-lance`. A Lance-formatted version of the [COCO Captions 2017](https://cocodataset.org/) corpus, redistributed via [`lmms-lab/COCO-Caption2017`](https://huggingface.co/datasets/lmms-lab/COCO-Caption2017). Each row is one image with **5–7 human-written captions**, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of the canonical caption — all stored inline and available directly from the Hub at `hf://datasets/lance-format/coco-captions-2017-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `text_emb` (ViT-B/32, 512-dim, cosine-normalized) — so cross-modal retrieval is one indexed lookup. * **All 5–7 raw captions kept in `captions`** alongside a `caption` canonical string used for full-text search. * **Pre-built ANN, FTS, and scalar indices** covering both embedding columns, the canonical caption, and `image_id`. ## Splits | Split | Rows | Notes | | ------------ | ------ | -------------------------------------------------- | | `val.lance` | 5,000 | Canonical COCO 2017 val set | | `test.lance` | 40,700 | Public test slice from `lmms-lab/COCO-Caption2017` | > The 2017 train split (118 k images, \~18 GB of source JPEGs) is intentionally not bundled here because the `lmms-lab/COCO-Caption2017` redistribution does not include it. To extend with train, run `coco_captions_2017/dataprep.py` against your local COCO 2017 train mirror. ## Schema | Column | Type | Notes | | ----------- | ------------------------------- | -------------------------------------------- | | `id` | `int64` | Row index within split (natural join key) | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `string` | COCO image id | | `filename` | `string` | Original filename (e.g. `000000179765.jpg`) | | `captions` | `list` | All 5–7 captions for the image | | `caption` | `string` | First caption — canonical text used for FTS | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | | `text_emb` | `fixed_size_list` | CLIP text embedding of the canonical caption | ## Pre-built indices * `IVF_PQ` on `image_emb` — image-side vector search (cosine) * `IVF_PQ` on `text_emb` — text-side vector search (cosine) * `INVERTED` (FTS) on `caption` — keyword and hybrid search * `BTREE` on `image_id` — fast lookup by COCO image 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/coco-captions-2017-lance", split="val", streaming=True) for row in hf_ds.take(3): print(row["caption"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") tbl = db.open_table("val") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/coco-captions-2017-lance/data/val.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/coco-captions-2017-lance --repo-type dataset --local-dir ./coco-captions-2017-lance > ``` > > Then point Lance or LanceDB at `./coco-captions-2017-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes cross-modal text→image retrieval a single call: encode a text query 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 `text_emb` already stored in row 42 as a runnable stand-in for "the CLIP encoding of a caption", so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") tbl = db.open_table("val") seed = ( tbl.search() .select(["text_emb", "caption"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["text_emb"], vector_column_name="image_emb") .metric("cosine") .select(["image_id", "caption"]) .limit(10) .to_list() ) print("query caption:", seed["caption"]) for r in hits: print(f" {r['image_id']:>12} {r['caption'][:70]}") ``` Because OpenAI-style CLIP embeddings are normalized, cosine is the right metric and the first hit will typically be the source image itself — a useful sanity check. Swap `vector_column_name="image_emb"` for `text_emb` to do text→text retrieval against the canonical captions instead. Because the dataset also ships an `INVERTED` index on `caption`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase like "yellow taxi" must literally appear in the caption but you still want CLIP to do the heavy lifting on visual similarity. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="image_emb") .vector(seed["text_emb"]) .text("a man riding a surfboard") .select(["image_id", "caption"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['image_id']:>12} {r['caption'][:70]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate A typical curation pass for a captioning or contrastive-training workflow combines a content filter on the captions with a structural filter on the image. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") tbl = db.open_table("val") candidates = ( tbl.search("surfer OR surfboard OR wave") .where("array_length(captions) >= 5", prefilter=True) .select(["image_id", "caption", "captions"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first caption: {candidates[0]['caption'][:80]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `image_id`s, 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 caption text 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 `num_captions` and a `long_caption` flag, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./coco-captions-2017-lance/data") # local copy required for writes tbl = db.open_table("val") tbl.add_columns({ "num_captions": "array_length(captions)", "long_caption": "length(caption) >= 80", }) ``` If the values you want to attach already live in another table (offline labels, classifier predictions, a second-pass caption from a different model), merge them in by joining on `image_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa labels = pa.table({ "image_id": pa.array(["179765", "000139"]), "scene_label": pa.array(["beach", "kitchen"]), }) tbl.merge(labels, on="image_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 second CLIP variant over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 CLIP-style contrastive run, project the JPEG bytes and a sampled caption; for a reranker or probe on top of frozen features, project the precomputed embeddings instead. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") tbl = db.open_table("val") train_ds = Permutation.identity(tbl).select_columns(["image", "caption"]) loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; decode the JPEG bytes, # tokenize the captions, encode, contrastive loss... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "text_emb"]` 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 training a lightweight reranker or a linear probe. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") tbl = db.open_table("val") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./coco-captions-2017-lance/data") local_tbl = local_db.open_table("val") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("val", version="clip-vitb32-v1") tbl_v5 = db.open_table("val", 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 embeddings 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 images and captions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/coco-captions-2017-lance/data") remote_tbl = remote_db.open_table("test") batches = ( remote_tbl.search("surfer OR surfboard OR wave") .where("array_length(captions) >= 5") .select(["image_id", "image", "caption", "captions", "image_emb", "text_emb"]) .to_batches() ) local_db = lancedb.connect("./coco-surf-subset") local_db.create_table("train", batches) ``` The resulting `./coco-surf-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/coco-captions-2017-lance/data` for `./coco-surf-subset`. ## Source & license Converted from [`lmms-lab/COCO-Caption2017`](https://huggingface.co/datasets/lmms-lab/COCO-Caption2017). Original COCO 2017 annotations are released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); the underlying images are subject to Flickr terms of service. Please review the [COCO Terms of Use](https://cocodataset.org/#termsofuse) before redistribution. ## Citation ``` @inproceedings{lin2014microsoft, title={Microsoft COCO: Common objects in context}, author={Lin, Tsung-Yi and Maire, Michael and Belongie, Serge and Hays, James and Perona, Pietro and Ramanan, Deva and Doll{\'a}r, Piotr and Zitnick, C Lawrence}, booktitle={European Conference on Computer Vision (ECCV)}, year={2014} } ``` # COCO 2017 Detection Source: https://docs.lancedb.com/datasets/coco-detection-2017 A Lance-formatted version of the COCO 2017 object detection benchmark, sourced from detection-datasets/coco. Each row is one image with its inline JPEG bytes, the full per-image list of bounding boxes, COCO 80-class category ids and names… Source dataset card and downloadable files for `lance-format/coco-detection-2017-lance`. A Lance-formatted version of the [COCO 2017 object detection benchmark](https://cocodataset.org/), sourced from [`detection-datasets/coco`](https://huggingface.co/datasets/detection-datasets/coco). Each row is one image with its inline JPEG bytes, the full per-image list of bounding boxes, COCO 80-class category ids and names, per-object areas, an OpenCLIP image embedding, and pre-built indices — all available directly from the Hub at `hf://datasets/lance-format/coco-detection-2017-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Per-object annotations as parallel list columns** — `bboxes`, `categories`, `category_names`, and `areas` are aligned position-for-position, so iterating boxes alongside their labels is a single row read. * **Pre-aggregated annotation summaries** — `num_objects` (int) and `categories_present` (deduped string list) precompute the predicates curation queries hit most. * **CLIP image embeddings** (`image_emb`, OpenCLIP ViT-B/32, 512-d, cosine-normalized) with a bundled `IVF_PQ` index for visual retrieval. ## Splits | Split | Rows | | ------------- | -------- | | `train.lance` | 117,000+ | | `val.lance` | 4,950+ | Total annotated boxes: \~860k train / \~37k val. ## Schema | Column | Type | Notes | | -------------------- | ------------------------------- | ------------------------------------------------------------------------ | | `id` | `int64` | Row index within split | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `int64` | COCO image id (natural join key) | | `width`, `height` | `int32` | Image dimensions in pixels | | `bboxes` | `list>` | Each box is `[x_min, y_min, x_max, y_max]` in absolute pixel coordinates | | `categories` | `list` | COCO 80-class id (0–79), aligned with `bboxes` | | `category_names` | `list` | Human-readable class name per object (e.g. `person`, `dog`) | | `areas` | `list` | Bounding-box area in pixels², aligned with `bboxes` | | `num_objects` | `int32` | Number of annotated objects in the image | | `categories_present` | `list` | Deduped class names — feeds the `LABEL_LIST` index | | `image_emb` | `fixed_size_list` | OpenCLIP ViT-B/32 image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `image_id` — fast lookup by COCO image id * `BTREE` on `num_objects` — range filters on image complexity * `LABEL_LIST` on `categories_present` — supports `array_has_any` / `array_has_all` for class-presence filtering ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/coco-detection-2017-lance", split="val", streaming=True) for row in hf_ds.take(3): print(row["image_id"], row["num_objects"], row["categories_present"][:5]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") tbl = db.open_table("val") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/coco-detection-2017-lance/data/val.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, ANN search, and any mutation are far faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/coco-detection-2017-lance --repo-type dataset --local-dir ./coco-detection-2017-lance > ``` > > Then point Lance or LanceDB at `./coco-detection-2017-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes approximate-nearest-neighbor visual retrieval a single call. In production you would encode a query image through the same OpenCLIP ViT-B/32 model used at ingest and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding stored on row 42 as a runnable stand-in, so the snippet works without loading any model. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") tbl = db.open_table("val") seed = ( tbl.search() .select(["image_emb", "image_id", "categories_present"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["image_id", "categories_present", "num_objects"]) .limit(10) .to_list() ) print("query categories:", seed["categories_present"]) for r in hits: print(f" image_id={r['image_id']:>10} n={r['num_objects']:>3} cats={r['categories_present'][:5]}") ``` Because the embeddings are cosine-normalized, the first hit will typically be the source image itself — a useful sanity check. Tune `nprobes` and `refine_factor` to trade recall against latency for your workload. ## Curate Curation for a detection workflow usually means picking images that contain a specific class combination, possibly bounded by scene complexity. The `LABEL_LIST` index on `categories_present` makes class-presence predicates trivial, and Lance evaluates them inside the same scan as range filters on `num_objects` or `width`/`height`. The bounded `.limit(500)` keeps the result small and inspectable, and the `image` column is left out of the projection so the candidate scan is dominated by annotation metadata, not JPEG bytes. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") tbl = db.open_table("val") candidates = ( tbl.search() .where( "array_has_all(categories_present, ['person', 'frisbee']) " "AND num_objects BETWEEN 3 AND 12", prefilter=True, ) .select(["image_id", "categories_present", "num_objects", "width", "height"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first image_id: {candidates[0]['image_id']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `image_id`s, or feed into the Evolve and Train workflows below. Swapping `array_has_all` for `array_has_any` widens recall to images containing any of the listed classes; replacing the structural predicate with `num_objects >= 10` selects busy scenes for crowd-detection ablations. ## 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 `has_person` flag, an `aspect_ratio`, and a `max_box_area` that surfaces the largest annotated object area per image — all 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./coco-detection-2017-lance/data") # local copy required for writes tbl = db.open_table("val") tbl.add_columns({ "has_person": "array_has_any(categories_present, ['person'])", "aspect_ratio": "CAST(width AS DOUBLE) / CAST(height AS DOUBLE)", "max_box_area": "array_max(areas)", "crowded": "num_objects >= 10", }) ``` If the values you want to attach already live in another table (offline predictions from a baseline detector, per-image difficulty scores, or a second-pass embedding), merge them in by joining on `image_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "image_id": pa.array([397133, 37777, 252219], type=pa.int64()), "baseline_map": pa.array([0.31, 0.48, 0.22]), }) tbl.merge(predictions, on="image_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 second detector over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 detector training run, project the JPEG bytes alongside the parallel annotation columns the loss consumes — boxes, category ids, and (optionally) areas. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns( ["image", "bboxes", "categories", "areas"] ) loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=4, collate_fn=lambda b: b) # detection targets are ragged for batch in loader: # batch is a list of dicts: decode each JPEG, stack the bboxes / categories # into the target dictionary your detector expects, forward, loss... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "categories_present"]` to `select_columns(...)` on the next run skips JPEG decoding entirely and reads only the cached 512-d vectors plus the deduped class list, which is the right shape for training a lightweight multi-label classifier or a class-presence probe on top of frozen 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") tbl = db.open_table("val") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./coco-detection-2017-lance/data") local_tbl = local_db.open_table("val") local_tbl.tags.create("detector-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("val", version="detector-baseline-v1") tbl_v5 = db.open_table("val", version=5) ``` Pinning supports two workflows. An evaluation harness locked to `detector-baseline-v1` keeps scoring against the exact same boxes and category ids while the dataset evolves in parallel; newly merged predictions or evolved columns 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 annotations, so changes in mAP 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/coco-detection-2017-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("array_has_any(categories_present, ['dog', 'cat']) AND num_objects >= 2") .select(["image_id", "image", "bboxes", "categories", "category_names", "areas", "num_objects", "categories_present", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./coco-pets-subset") local_db.create_table("train", batches) ``` The resulting `./coco-pets-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/coco-detection-2017-lance/data` for `./coco-pets-subset`. ## Source & license Converted from [`detection-datasets/coco`](https://huggingface.co/datasets/detection-datasets/coco). COCO annotations are released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); the underlying images are subject to Flickr terms of service. See the [COCO Terms of Use](https://cocodataset.org/#termsofuse) before redistribution. ## Citation ``` @inproceedings{lin2014microsoft, title={Microsoft COCO: Common objects in context}, author={Lin, Tsung-Yi and Maire, Michael and Belongie, Serge and Hays, James and Perona, Pietro and Ramanan, Deva and Doll{\'a}r, Piotr and Zitnick, C Lawrence}, booktitle={European Conference on Computer Vision (ECCV)}, year={2014} } ``` # DocVQA Source: https://docs.lancedb.com/datasets/docvqa A Lance-formatted version of DocVQA, a benchmark for visual question answering over document images such as industry and government scans, multi-page reports, forms, and receipts, redistributed via lmms-lab/DocVQA (DocVQA config). Each row carries… Source dataset card and downloadable files for `lance-format/docvqa-lance`. A Lance-formatted version of [DocVQA](https://www.docvqa.org/), a benchmark for visual question answering over document images such as industry and government scans, multi-page reports, forms, and receipts, redistributed via [`lmms-lab/DocVQA`](https://huggingface.co/datasets/lmms-lab/DocVQA) (`DocVQA` config). Each row carries the page image as inline JPEG bytes, the question and reference answer span(s), the original DocVQA question-type tags, UCSF Industry Documents Library provenance, and paired CLIP embeddings for the image and the question — all available directly from the Hub at `hf://datasets/lance-format/docvqa-lance/data`. ## Key features * **Inline page image bytes** in the `image` column — no sidecar files, no document folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `question_emb` (ViT-B/32, 512-dim, cosine-normalized) — so visual and textual retrieval are one indexed lookup. * **All reference answer spans preserved in `answers`** alongside a canonical `answer` string used for full-text search. * **Pre-built ANN, FTS, scalar, and label-list indices** covering both embedding columns, the question and answer text, the document ids, and the `question_types` tag list. ## Splits | Split | Rows | Notes | | ------------------ | ----- | ---------------------------------------- | | `validation.lance` | 5,349 | Canonical DocVQA validation set | | `test.lance` | 5,188 | Public test slice from `lmms-lab/DocVQA` | ## Schema | Column | Type | Notes | | ----------------------- | ------------------------------- | -------------------------------------------------------- | | `id` | `int64` | Row index within split (natural join key) | | `image` | `large_binary` | Inline JPEG bytes (page image) | | `image_id` | `string?` | DocVQA `docId` (alias of `doc_id`) | | `question_id` | `string?` | DocVQA `questionId` | | `question` | `string` | Natural-language question | | `answers` | `list` | Reference answer span(s) | | `answer` | `string` | First reference answer — canonical, used for FTS | | `doc_id` | `string?` | DocVQA document id | | `ucsf_document_id` | `string?` | UCSF Industry Documents Library id | | `ucsf_document_page_no` | `string?` | Page number within the source document | | `data_split` | `string?` | Original split label from the source | | `question_types` | `list` | DocVQA question-type tags (`form`, `figure`, `table`, …) | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | | `question_emb` | `fixed_size_list` | CLIP text embedding of the question | ## Pre-built indices * `IVF_PQ` on `image_emb` — image-side vector search (cosine) * `IVF_PQ` on `question_emb` — text-side vector search (cosine) * `INVERTED` (FTS) on `question` and `answer` — keyword and hybrid search * `BTREE` on `image_id`, `question_id`, `doc_id` — fast lookup by document or question id * `LABEL_LIST` on `question_types` — set-membership filtering over question-type tags ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/docvqa-lance", split="validation", 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](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/docvqa-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/docvqa-lance --repo-type dataset --local-dir ./docvqa-lance > ``` > > Then point Lance or LanceDB at `./docvqa-lance/data`. ## Search The bundled `IVF_PQ` index on `question_emb` makes question-to-question retrieval a single call: encode a query with the same CLIP model used at ingest (ViT-B/32, cosine-normalized) and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the `question_emb` already stored in row 42 as a runnable stand-in, so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") tbl = db.open_table("validation") 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") .select(["question_id", "question", "answer", "question_types"]) .limit(10) .to_list() ) print("query:", seed["question"]) for r in hits: print(f" {r['question_id']:>8} {r['question'][:60]} -> {r['answer']}") ``` Swap `vector_column_name="question_emb"` for `image_emb` to retrieve pages whose visual layout is similar to a given embedding — useful when you want to find other forms or invoices that look like a seed page. Because the dataset also ships an `INVERTED` index on `question` and `answer`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase like "invoice total" or "date of birth" must literally appear in the question but you still want CLIP to do the heavy lifting on semantic similarity. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="question_emb") .vector(seed["question_emb"]) .text("invoice total") .select(["question_id", "question", "answer"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['question_id']:>8} {r['question'][:60]} -> {r['answer']}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate A typical curation pass for a document-VQA workflow combines a content filter on the question with a structural filter on the question-type tags. 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. The example below collects form-style questions that mention a date, which is a common slice for evaluating form-understanding behaviour. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search("date") .where("array_has_any(question_types, ['form'])", prefilter=True) .select(["question_id", "doc_id", "question", "answer", "question_types"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['question'][:80]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `question_id`s, 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 question and answer text rather than page JPEGs. ## 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 `answer_length`, an `is_form_question` flag, and a `has_table` 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 split first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./docvqa-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "answer_length": "length(answer)", "is_form_question": "array_has_any(question_types, ['form'])", "has_table": "array_has_any(question_types, ['table/list'])", }) ``` If the values you want to attach already live in another table (OCR-extracted page text, model predictions, layout-detector outputs), merge them in by joining on `question_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "question_id": pa.array(["49153", "49154", "49155"]), "pred_answer": pa.array(["$1,234.56", "John Doe", "2018-04-12"]), "is_correct": pa.array([True, True, False]), }) 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 (e.g., running OCR or a layout model over the page bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 fine-tuning a document-VLM, project the page bytes plus the question and answer; columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") tbl = db.open_table("validation") train_ds = Permutation.identity(tbl).select_columns(["image", "question", "answer"]) loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; decode the JPEG bytes, # tokenize the question/answer pair, forward, backward... ... ``` 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 training a lightweight answer-classifier or a linear probe on top of frozen features. ## Versioning Every mutation to a Lance dataset, whether it adds a column, merges predictions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./docvqa-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("eval-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="eval-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. An evaluation harness locked to `eval-v1` keeps producing comparable scores while the dataset evolves in parallel — newly added prediction columns 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 pages 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/docvqa-lance/data") remote_tbl = remote_db.open_table("validation") batches = ( remote_tbl.search("date") .where("array_has_any(question_types, ['form'])") .select(["id", "image", "question_id", "doc_id", "question", "answer", "question_types", "image_emb", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./docvqa-forms-subset") local_db.create_table("validation", batches) ``` The resulting `./docvqa-forms-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/docvqa-lance/data` for `./docvqa-forms-subset`. ## Source & license Converted from [`lmms-lab/DocVQA`](https://huggingface.co/datasets/lmms-lab/DocVQA). DocVQA is released under the MIT license; the underlying documents come from the [UCSF Industry Documents Library](https://www.industrydocuments.ucsf.edu/) — review their access conditions before redistribution. ## Citation ``` @inproceedings{mathew2021docvqa, title={DocVQA: A Dataset for VQA on Document Images}, author={Mathew, Minesh and Karatzas, Dimosthenis and Jawahar, CV}, booktitle={Proceedings of the IEEE/CVF Winter Conference on Applications of Computer Vision (WACV)}, year={2021} } ``` # EuroSAT Source: https://docs.lancedb.com/datasets/eurosat A Lance-formatted version of EuroSAT, the canonical Sentinel-2 RGB land-cover benchmark, sourced from blanchon/EuroSAT_RGB. Each row is a single 64×64 RGB tile with its integer class id, the human-readable class name, and a cosine-normalized… Source dataset card and downloadable files for `lance-format/eurosat-lance`. A Lance-formatted version of [EuroSAT](https://github.com/phelber/eurosat), the canonical Sentinel-2 RGB land-cover benchmark, sourced from [`blanchon/EuroSAT_RGB`](https://huggingface.co/datasets/blanchon/EuroSAT_RGB). Each row is a single 64×64 RGB tile with its integer class id, the human-readable class name, and a cosine-normalized OpenCLIP image embedding — all stored inline and available directly from the Hub at `hf://datasets/lance-format/eurosat-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar TIF folders, no per-class subdirectories. * **Pre-computed OpenCLIP image embeddings** (`image_emb`, ViT-B/32, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for similarity search. * **Both label representations** — integer `label` (0-9) and string `label_name` — with scalar indices on both for fast class filters. * **One columnar dataset** — scan labels and embeddings cheaply, fetch tile bytes only for the rows you actually need. ## Splits | Split | Rows | Notes | | ------------------ | ------ | ------------------- | | `train.lance` | 16,200 | Training split | | `validation.lance` | 5,400 | Validation split | | `test.lance` | 5,400 | Held-out test split | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `int64` | Row index within the split (natural join key) | | `image` | `large_binary` | Inline JPEG bytes (64×64 RGB Sentinel-2 tile) | | `label` | `int32` | Class id (0-9) | | `label_name` | `string` | One of `Annual_Crop`, `Forest`, `Herbaceous_Vegetation`, `Highway`, `Industrial_Buildings`, `Pasture`, `Permanent_Crop`, `Residential_Buildings`, `River`, `SeaLake` | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `label` — fast equality / range filters by class id * `BITMAP` on `label_name` — fast set-membership filters by class name ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/eurosat-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/eurosat-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/eurosat-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/eurosat-lance --repo-type dataset --local-dir ./eurosat-lance > ``` > > Then point Lance or LanceDB at `./eurosat-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes visually-similar-tile retrieval a single call. In production you would encode a query tile through the same OpenCLIP `ViT-B-32` model used at ingest (cosine-normalized) and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding already stored in row 42 as a runnable stand-in, so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/eurosat-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "label_name"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label_name"]) .limit(10) .to_list() ) print(f"reference tile class: {seed['label_name']}") for r in hits: print(f" id={r['id']:>6} {r['label_name']}") ``` Because the embeddings are cosine-normalized at ingest, `metric="cosine")` is the right choice and the first hit will typically be the seed tile itself — a useful sanity check. Tune `nprobes` and `refine_factor` to trade recall against latency for your workload. ## Curate A typical curation pass for a land-cover classification or retrieval study narrows the dataset to a single class and then retrieves the visually closest tiles to a seed. Lance evaluates the vector search and the metadata filter inside a single query, so the candidate set comes back already filtered. The example below pulls the 500 forest tiles most similar to a chosen seed; the bounded `.limit(500)` keeps the output small enough to inspect or hand off. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/eurosat-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb"]) .limit(1) .offset(0) .to_list()[0] ) candidates = ( tbl.search(seed["image_emb"]) .where("label_name = 'Forest'", prefilter=True) .select(["id", "label", "label_name"]) .limit(500) .to_list() ) print(f"{len(candidates)} Forest candidates") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of row 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 small metadata payload 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 a coarse `is_urban` flag that captures whether a tile belongs to one of the built-environment classes, useful as a direct predicate in later `where` clauses without re-evaluating the class set 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 first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./eurosat-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "is_urban": "label_name IN ('Highway', 'Industrial_Buildings', 'Residential_Buildings')", }) ``` If the values you want to attach already live in another table (a coarse climate label per class, an external aesthetic score, model predictions from a separate eval), merge them in by joining on `label_name`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa climate = pa.table({ "label_name": pa.array(["Forest", "Pasture", "SeaLake", "River"]), "climate_zone": pa.array(["temperate", "temperate", "marine", "freshwater"]), }) tbl.merge(climate, on="label_name") ``` 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 an alternative remote-sensing model over the tile bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/eurosat-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the JPEG bytes, forward through a CNN or ViT, cross-entropy loss... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` 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 training a linear probe or a lightweight classifier head on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/eurosat-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./eurosat-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 columns 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 tiles, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/eurosat-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("label_name IN ('Forest', 'River', 'SeaLake')") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./eurosat-natural-subset") local_db.create_table("train", batches) ``` The resulting `./eurosat-natural-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/eurosat-lance/data` for `./eurosat-natural-subset`. ## Source & license Converted from [`blanchon/EuroSAT_RGB`](https://huggingface.co/datasets/blanchon/EuroSAT_RGB). EuroSAT is released under the MIT license by Helber et al. The underlying Sentinel-2 imagery is © European Space Agency, made available under the [Copernicus open data policy](https://www.copernicus.eu/en/access-data/copyright-and-licences). ## Citation ``` @inproceedings{helber2019eurosat, title={EuroSAT: A novel dataset and deep learning benchmark for land use and land cover classification}, author={Helber, Patrick and Bischke, Benjamin and Dengel, Andreas and Borth, Damian}, journal={IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing}, year={2019} } ``` # Fashion-MNIST Source: https://docs.lancedb.com/datasets/fashion-mnist A Lance-formatted version of Fashion-MNIST covering 70,000 28×28 grayscale clothing images across ten balanced apparel classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image… Source dataset card and downloadable files for `lance-format/fashion-mnist-lance`. A Lance-formatted version of [Fashion-MNIST](https://huggingface.co/datasets/zalando-datasets/fashion_mnist) covering 70,000 28×28 grayscale clothing images across ten balanced apparel classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image embedding, all backed by a bundled `IVF_PQ` vector index plus scalar indices on the label columns and available directly from the Hub at `hf://datasets/lance-format/fashion-mnist-lance/data`. ## Key features * **Inline PNG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (OpenCLIP `ViT-B-32` / `laion2b_s34b_b79k`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index. * **Scalar indices on both label columns** — `BTREE` on `label` and `BITMAP` on `label_name` — so apparel-class filters and class-conditioned search are constant-time lookups. * **One columnar dataset** — scan labels cheaply, then fetch image bytes only for the rows you want. ## Splits | Split | Rows | | ------------- | ------ | | `train.lance` | 60,000 | | `test.lance` | 10,000 | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `id` | `int64` | Row index within the split (natural join key for merges) | | `image` | `large_binary` | Inline PNG bytes (28×28 grayscale) | | `label` | `int32` | Class id (0–9) | | `label_name` | `string` | One of `T-shirt_top`, `Trouser`, `Pullover`, `Dress`, `Coat`, `Sandal`, `Shirt`, `Sneaker`, `Bag`, `Ankle_boot` | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | > The original Fashion-MNIST class strings `T-shirt/top` and `Ankle boot` are sanitized to `T-shirt_top` and `Ankle_boot` for use as filename-safe identifiers, so SQL filters on `label_name` should reference the underscored form. ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `label` — fast equality and range filters on the class id * `BITMAP` on `label_name` — fast filters across the ten class names ## 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 if your pipeline already speaks `Dataset` / `IterableDataset` or you want a quick streaming sample without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/fashion-mnist-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label"], row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/fashion-mnist-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/fashion-mnist-lance --repo-type dataset --local-dir ./fashion-mnist-lance > ``` > > Then point Lance or LanceDB at `./fashion-mnist-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` turns nearest-neighbor lookup on the 512-d CLIP space into a single call. In production you would encode a query image (or, for cross-modal text→image lookup, a tokenized prompt like "a black ankle boot") through OpenCLIP `ViT-B-32` at runtime and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding already stored in row 42 as a runnable stand-in so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "label_name"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label", "label_name"]) .limit(10) .to_list() ) print("query class:", seed["label_name"]) for r in hits: print(f" id={r['id']:>5} {r['label_name']}") ``` Because the embeddings are cosine-normalized and CLIP separates apparel categories cleanly, near-neighbors of a seed image are typically dominated by the seed's own class, with the most confusable garments (Shirt vs T-shirt\_top, Sneaker vs Sandal) showing up next. Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency. ## Curate A typical curation pass for an apparel-classification workflow narrows the table to a confusable subset of classes (for example, the three upper-body garments that share silhouettes) before sampling. Because both label columns are indexed, the filter resolves without scanning the embedding or image bytes; the bounded `.limit(500)` keeps the output small enough to inspect or hand off as a manifest of row ids. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where("label_name IN ('Shirt', 'T-shirt_top', 'Pullover')", prefilter=True) .select(["id", "label", "label_name"]) .limit(500) .to_list() ) print(f"{len(candidates)} upper-body-garment candidates") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `image_emb` columns are never read, so the network traffic for a 500-row candidate scan is dominated by the tiny label payload. ## 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_footwear` flag that groups the three shoe-like classes and an `is_target_class` flag for one-vs-rest experiments, 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 corpus first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./fashion-mnist-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "is_footwear": "label_name IN ('Sandal', 'Sneaker', 'Ankle_boot')", "is_target_class": "label = 6", }) ``` If the values you want to attach already live in another table (offline labels from a stronger model, classifier predictions, per-row confidence scores), merge them in by joining on the `id` column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "pred_label": pa.array([9, 0, 3], type=pa.int32()), "pred_conf": pa.array([0.94, 0.81, 0.77]), }) tbl.merge(predictions, 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 second image encoder over the inline PNG bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the PNG bytes, normalize to [0, 1], forward, backward... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` to `select_columns(...)` on the next run skips PNG decoding entirely and reads only the cached 512-d vectors, which is the right shape for training a linear probe or a lightweight reranker on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./fashion-mnist-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 prediction columns or relabelings 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 labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/fashion-mnist-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("label_name IN ('Shirt', 'T-shirt_top', 'Pullover')") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./fashion-mnist-upper-body") local_db.create_table("train", batches) ``` The resulting `./fashion-mnist-upper-body` 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/fashion-mnist-lance/data` for `./fashion-mnist-upper-body`. ## Source & license Converted from [`zalando-datasets/fashion_mnist`](https://huggingface.co/datasets/zalando-datasets/fashion_mnist). Released under the MIT license. ## Citation ``` @online{xiao2017fashionmnist, title={Fashion-MNIST: a Novel Image Dataset for Benchmarking Machine Learning Algorithms}, author={Xiao, Han and Rasul, Kashif and Vollgraf, Roland}, year={2017}, eprint={1708.07747}, archivePrefix={arXiv}, primaryClass={cs.LG} } ``` # FineWeb-Edu Source: https://docs.lancedb.com/datasets/fineweb-edu A Lance-formatted version of FineWeb-Edu — over 1.5 billion educational web passages with cleaned text, source metadata, language detection signals, and 384-dim text embeddings — available directly from the Hub at… Source dataset card and downloadable files for `lance-format/fineweb-edu`. A Lance-formatted version of [FineWeb-Edu](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu) — **over 1.5 billion educational web passages** with cleaned text, source metadata, language detection signals, and 384-dim text embeddings — available directly from the Hub at `hf://datasets/lance-format/fineweb-edu/data/train.lance`. ## Key features * **Cleaned passage text** in the `text` column with the source `url` and `title` carried alongside. * **Language detection signals** (`language`, `language_probability`) for filtered subsets. * **Pre-computed 384-dim text embeddings** in `text_embedding`, ready for ANN search once an index is built locally. * **One columnar dataset** — scan metadata cheaply, project just the columns each query needs, defer the heavy `text` and `text_embedding` reads to the rows that matter. > **No pre-built indices on the Hub copy yet.** At 1.5 B+ rows the on-disk indices are too large to ship comfortably alongside the data on the Hub. The Search, Curate, Evolve, and Train sections below describe the same APIs you'd use against a fully indexed dataset, but vector and full-text examples assume a local copy with `IVF_PQ` and `INVERTED` indices built once after download. See the Materialize-a-subset section at the end for a focused-subset workflow that makes indexing tractable. ## Splits `train.lance` ## Schema | Column | Type | Notes | | ------------------------------ | ------------------------------- | ---------------------------------------------------------------------------- | | `text` | `string` | Cleaned passage body | | `title` | `string` | Page or article title when available | | `url` | `string` | Canonical source URL | | `language` | `string` | Detected language code (e.g., `en`) | | `language_probability` | `float32` | Confidence of the language detector | | `text_embedding` | `fixed_size_list` | Passage embedding for retrieval | | *FineWeb-Edu quality metadata* | — | Heuristic scores and length statistics carried over from the upstream corpus | ## Pre-built indices None bundled at present. Build the recommended indices on a local copy: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./fineweb-edu/data") tbl = db.open_table("train") tbl.create_index( metric="cosine", vector_column_name="text_embedding", index_type="IVF_PQ", num_partitions=2048, num_sub_vectors=96, ) tbl.create_fts_index("text", replace=True) ``` Both indices live next to the data, so subsequent queries against the same local path pick them up automatically. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/fineweb-edu", split="train", streaming=True) for row in hf_ds.take(3): print(row["title"] or row["url"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fineweb-edu/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/fineweb-edu/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 at 1.5 B+ rows random access and any kind of search are dramatically faster against a local copy, and ANN / FTS require local indices anyway: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/fineweb-edu --repo-type dataset --local-dir ./fineweb-edu > ``` > > Then point Lance or LanceDB at `./fineweb-edu/data`. For most workflows, the Materialize-a-subset section is a better starting point than downloading the full 1.5 B-row corpus. ## Search Once an `IVF_PQ` index exists on `text_embedding`, dense retrieval is a single call. In production you would encode a query string through the same 384-dim text encoder used at ingest and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./fineweb-edu/data") # local copy with the indices from the section above tbl = db.open_table("train") seed = ( tbl.search() .select(["text_embedding", "url"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["text_embedding"]) .metric("cosine") .where("language = 'en' AND language_probability > 0.9", prefilter=True) .select(["title", "url", "text"]) .limit(10) .to_list() ) for r in hits: print(f"{r['url']}\n {(r['title'] or '')[:80]}") ``` The result set carries only the projected columns. The `text_embedding` vector is never read on the result side, and the `text` body is fetched only for the ten passages that actually came back, keeping the working set small even though the corpus is enormous. Because the recommended setup also builds an `INVERTED` index on `text`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase must literally appear in the passage but the dense side still does most of the ranking. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["text_embedding"]) .text("quantum computing") .where("language = 'en'", prefilter=True) .select(["title", "url", "text"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f"{r['url']}\n {(r['title'] or '')[:80]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate A typical curation pass over a web corpus starts with a metadata filter — pick high-confidence English, drop short or low-quality fragments, restrict to a domain — before any text gets read. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(1000)` makes it cheap to inspect. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fineweb-edu/data") tbl = db.open_table("train") candidates = ( tbl.search() .where( "language = 'en' " "AND language_probability > 0.95 " "AND length(text) >= 1000", prefilter=True, ) .select(["url", "title", "language_probability"]) .limit(1000) .to_list() ) print(f"{len(candidates)} candidates; first url: {candidates[0]['url']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of URLs, or hand to the Materialize-a-subset section below for export to a writable local copy. Neither the `text` body nor the `text_embedding` vector is read by this scan, so a 1000-row curation pass against the Hub moves only kilobytes of metadata even though the underlying table is in the billions. ## 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 `text_length` and a `long_passage` flag, 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 a larger slice first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./fineweb-edu/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "text_length": "length(text)", "long_passage": "length(text) >= 1000", }) ``` If the values you want to attach already live in another table (offline labels, topic classifications, alternate embeddings from a stronger model), merge them in by joining on `url`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa labels = pa.table({ "url": pa.array(["https://example.com/a", "https://example.com/b"]), "topic": pa.array(["math", "history"]), }) tbl.merge(labels, on="url") ``` 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 text), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 language-model pretraining the natural projection is just the `text` column; for a retrieval probe or a reranker on top of frozen features, project the precomputed embedding instead. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./fineweb-edu/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["text"]) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=8) for batch in loader: # batch carries only the projected columns; tokenize, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["text_embedding"]` to `select_columns(...)` on the next run reads only the 384-d vectors and skips the text body entirely, which is the right shape for training a lightweight retrieval head 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/fineweb-edu/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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./fineweb-edu/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("english-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="english-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A retrieval system locked to `english-v1` keeps returning stable results while the dataset evolves in parallel — newly added embeddings 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 passages, so changes in metrics reflect model changes rather than data drift. Neither workflow needs shadow copies or external manifest tracking. ## Materialize a subset At 1.5 B+ rows, very few workflows want the full corpus on local disk. The practical entry point 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. The result is a writable LanceDB database scoped to the rows that actually matter for the downstream task, sized to index and iterate cheaply. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/fineweb-edu/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where( "language = 'en' " "AND language_probability > 0.95 " "AND length(text) >= 1000" ) .select(["url", "title", "text", "language", "language_probability", "text_embedding"]) .to_batches() ) local_db = lancedb.connect("./fineweb-edu-en") local_db.create_table("train", batches) ``` The resulting `./fineweb-edu-en` is a first-class LanceDB database. Build the recommended indices on it once (the same `create_index` / `create_fts_index` calls shown in the Pre-built indices section, pointed at the local path), and every snippet in the Search, Evolve, Train, and Versioning sections above works against it by swapping `hf://datasets/lance-format/fineweb-edu/data` for `./fineweb-edu-en`. ## Source & license Converted from [`HuggingFaceFW/fineweb-edu`](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu). FineWeb-Edu is distributed under [ODC-BY 1.0](https://opendatacommons.org/licenses/by/1-0/); individual document content remains subject to the rights of the original publishers. Review the [upstream dataset card](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu) before downstream use. ## Citation ``` @misc{lozhkov2024finewebedu, title = {FineWeb-Edu: the Finest Collection of Educational Content the Web Has to Offer}, author = {Lozhkov, Anton and Ben Allal, Loubna and von Werra, Leandro and Wolf, Thomas}, year = {2024}, url = {https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu} } ``` # Flickr30k Source: https://docs.lancedb.com/datasets/flickr30k A Lance-formatted version of Flickr30k, redistributed via lmms-lab/flickr30k. Each row is one image with 5 human-written captions, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of the canonical caption — all… Source dataset card and downloadable files for `lance-format/flickr30k-lance`. A Lance-formatted version of [Flickr30k](https://shannon.cs.illinois.edu/DenotationGraph/), redistributed via [`lmms-lab/flickr30k`](https://huggingface.co/datasets/lmms-lab/flickr30k). Each row is one image with **5 human-written captions**, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of the canonical caption — all stored inline and available directly from the Hub at `hf://datasets/lance-format/flickr30k-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `text_emb` (ViT-B/32, 512-dim, cosine-normalized) — so cross-modal retrieval is one indexed lookup. * **All 5 raw captions kept in `captions`** alongside a `caption` canonical string used for full-text search. * **Pre-built ANN, FTS, and scalar indices** covering both embedding columns, the canonical caption, and `image_id`. ## Splits | Split | Rows | Notes | | ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | | `train.lance` | 31,783 | All Flickr30k images; the `lmms-lab/flickr30k` redistribution merges the original train/val/test labels into a single split | ## Schema | Column | Type | Notes | | ----------- | ------------------------------- | -------------------------------------------- | | `id` | `int64` | Row index within split (natural join key) | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `string` | Original Flickr image id | | `filename` | `string?` | Original filename (e.g. `1000092795.jpg`) | | `captions` | `list` | All 5 captions for the image | | `caption` | `string` | First caption — canonical text used for FTS | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | | `text_emb` | `fixed_size_list` | CLIP text embedding of the canonical caption | ## Pre-built indices * `IVF_PQ` on `image_emb` — image-side vector search (cosine) * `IVF_PQ` on `text_emb` — text-side vector search (cosine) * `INVERTED` (FTS) on `caption` — keyword and hybrid search * `BTREE` on `image_id` — fast lookup by Flickr image 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/flickr30k-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["caption"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/flickr30k-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, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/flickr30k-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/flickr30k-lance --repo-type dataset --local-dir ./flickr30k-lance > ``` > > Then point Lance or LanceDB at `./flickr30k-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes cross-modal text→image retrieval a single call: encode a text query 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 `text_emb` already stored in row 42 as a runnable stand-in for "the CLIP encoding of a caption", so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/flickr30k-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["text_emb", "caption"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["text_emb"], vector_column_name="image_emb") .metric("cosine") .select(["image_id", "caption"]) .limit(10) .to_list() ) print("query caption:", seed["caption"]) for r in hits: print(f" {r['image_id']:>12} {r['caption'][:70]}") ``` Because OpenAI-style CLIP embeddings are normalized, cosine is the right metric and the first hit will typically be the source image itself — a useful sanity check. Swap `vector_column_name="image_emb"` for `text_emb` to do text→text retrieval against the canonical captions instead. Because the dataset also ships an `INVERTED` index on `caption`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase like "dog playing in the snow" must literally appear in the caption but you still want CLIP to do the heavy lifting on visual similarity. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="image_emb") .vector(seed["text_emb"]) .text("dog playing in the snow") .select(["image_id", "caption"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['image_id']:>12} {r['caption'][:70]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate A typical curation pass for a captioning or contrastive-training workflow combines a content filter on the captions with a structural filter on the row. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/flickr30k-lance/data") tbl = db.open_table("train") candidates = ( tbl.search("surfer OR surfboard OR wave") .where("array_length(captions) = 5", prefilter=True) .select(["image_id", "caption", "captions"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first caption: {candidates[0]['caption'][:80]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `image_id`s, 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 caption text 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 `num_captions` and a `long_caption` flag, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./flickr30k-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "num_captions": "array_length(captions)", "long_caption": "length(caption) >= 80", }) ``` If the values you want to attach already live in another table (offline labels, classifier predictions, an aesthetic or NSFW score, a second-pass caption from a different model), merge them in by joining on `image_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa labels = pa.table({ "image_id": pa.array(["1000092795", "10002456"]), "scene_label": pa.array(["outdoor", "indoor"]), }) tbl.merge(labels, on="image_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 second CLIP variant over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 CLIP-style contrastive run, project the JPEG bytes and a sampled caption; for a reranker or probe on top of frozen features, project the precomputed embeddings instead. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/flickr30k-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "caption"]) loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; decode the JPEG bytes, # tokenize the captions, encode, contrastive loss... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "text_emb"]` 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 training a lightweight reranker or a linear probe. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/flickr30k-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./flickr30k-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 embeddings 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 images and captions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/flickr30k-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search("surfer OR surfboard OR wave") .where("array_length(captions) = 5") .select(["image_id", "image", "caption", "captions", "image_emb", "text_emb"]) .to_batches() ) local_db = lancedb.connect("./flickr30k-surf-subset") local_db.create_table("train", batches) ``` The resulting `./flickr30k-surf-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/flickr30k-lance/data` for `./flickr30k-surf-subset`. ## Source & license Converted from [`lmms-lab/flickr30k`](https://huggingface.co/datasets/lmms-lab/flickr30k), which is itself a parquet redistribution of the [original Flickr30k corpus](https://shannon.cs.illinois.edu/DenotationGraph/). Original images come from Flickr; review the Flickr30k licensing terms before redistribution. ## Citation ``` @article{young2014image, title={From image descriptions to visual denotations: New similarity metrics for semantic inference over event descriptions}, author={Young, Peter and Lai, Alice and Hodosh, Micah and Hockenmaier, Julia}, journal={Transactions of the Association for Computational Linguistics}, volume={2}, pages={67--78}, year={2014} } ``` # Food-101 Source: https://docs.lancedb.com/datasets/food101 A Lance-formatted version of Food-101, the fine-grained dish-classification benchmark of 101,000 photos spread evenly across 101 dish classes, sourced from ethz/food101. Each row carries the inline JPEG bytes, the integer label, the human-readable… Source dataset card and downloadable files for `lance-format/food101-lance`. A Lance-formatted version of [Food-101](https://www.kaggle.com/datasets/dansbecker/food-101), the fine-grained dish-classification benchmark of 101,000 photos spread evenly across 101 dish classes, sourced from [`ethz/food101`](https://huggingface.co/datasets/ethz/food101). Each row carries the inline JPEG bytes, the integer `label`, the human-readable `label_name`, and a cosine-normalized CLIP image embedding, all available directly from the Hub at `hf://datasets/lance-format/food101-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (`image_emb`, OpenCLIP `ViT-B-32`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for similarity search. * **Both numeric and string labels** (`label`, `label_name`) so filters can target either the class id or the dish name without an external mapping table. * **Scalar indices on both label columns** so class-based curation is a quick predicate rather than a full scan. ## Splits | Split | Rows | Notes | | ------------------ | ------ | ----------------------------------------------------- | | `train.lance` | 75,750 | Canonical Food-101 train split (750 images per class) | | `validation.lance` | 25,250 | Canonical Food-101 test split (250 images per class) | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | --------------------------------------------------------------------------- | | `id` | `int64` | Row index within split (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes (256x256, quality 92) | | `label` | `int32` | Class id (0–100) | | `label_name` | `string` | One of 101 dish names, underscore-spaced (`apple_pie`, `baby_back_ribs`, …) | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `label` — fast lookup by class id * `BITMAP` on `label_name` — fast lookup by class name ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/food101-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") tbl = db.open_table("validation") 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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/food101-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/food101-lance --repo-type dataset --local-dir ./food101-lance > ``` > > Then point Lance or LanceDB at `./food101-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes approximate-nearest-neighbor search a single call. In production you would encode a query photo through the same OpenCLIP `ViT-B-32` model used at ingest and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding stored in row 0 as a runnable stand-in so the snippet works without a model loaded; the first hit is expected to be the seed image itself, which is a useful sanity check on the index. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") tbl = db.open_table("validation") seed = ( tbl.search() .select(["image_emb", "label_name"]) .limit(1) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label_name"]) .limit(10) .to_list() ) print("seed dish:", seed["label_name"]) for r in hits: print(f" {r['id']:>6} {r['label_name']}") ``` Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency for your workload. ## Curate A typical curation pass for a fine-grained classifier combines a class-based filter with the bundled vector search to assemble a small, focused candidate set. The `BITMAP` index on `label_name` makes the predicate effectively free, and the bounded `.limit(200)` keeps the result small enough to inspect or hand off to a training run. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where("label_name IN ('sushi', 'sashimi', 'ramen')") .select(["id", "label", "label_name"]) .limit(200) .to_list() ) print(f"{len(candidates)} candidate Japanese-cuisine rows; first: {candidates[0]['label_name']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `image_emb` columns are never read by this query, so the network traffic is dominated by the small label fields rather than JPEG bytes or vectors. ## 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 coarse cuisine bucket and an `is_target_dish` flag for a focused training run, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./food101-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "is_target_dish": "label_name IN ('sushi', 'ramen', 'pho')", "is_dessert": "label_name IN ('apple_pie', 'cheesecake', 'tiramisu', 'ice_cream', 'donuts')", }) ``` If the values you want to attach already live in another table (offline labels, a second classifier's predictions, human-verified taste tags), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2]), "model_v2_pred": pa.array(["sushi", "sashimi", "sushi"]), }) tbl.merge(predictions, 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 second embedding model over the JPEG bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. For a from-scratch image classifier, project the JPEG bytes and the integer label; for a linear probe or reranker on top of frozen CLIP features, swap the projection to the embedding column and skip JPEG decoding entirely. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the JPEG bytes, forward, cross-entropy against `label`... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` to `select_columns(...)` on the next run reads only the cached 512-d vectors and the label, which is the right shape for a linear probe. ## Versioning Every mutation to a Lance dataset, whether it adds a column, merges predictions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./food101-lance/data") local_tbl = local_db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="clip-vitb32-v1") tbl_v5 = db.open_table("validation", 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 columns 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 images and labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/food101-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("label_name IN ('sushi', 'sashimi', 'ramen', 'pho', 'dumplings')") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./food101-asian-subset") local_db.create_table("train", batches) ``` The resulting `./food101-asian-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/food101-lance/data` for `./food101-asian-subset`. ## Source & license Converted from [`ethz/food101`](https://huggingface.co/datasets/ethz/food101). The Food-101 dataset is by Bossard et al. (ETH Zurich) — see the [original dataset page](https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/) for licensing details. ## Citation ``` @inproceedings{bossard2014food, title={Food-101 -- Mining Discriminative Components with Random Forests}, author={Bossard, Lukas and Guillaumin, Matthieu and Van Gool, Luc}, booktitle={European Conference on Computer Vision (ECCV)}, year={2014} } ``` # GQA testdev-balanced Source: https://docs.lancedb.com/datasets/gqa-testdev-balanced 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… 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`](https://huggingface.co/datasets/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 row** — `image_emb` and `question_emb` (512-dim, cosine-normalized) — for cross-modal retrieval as one indexed lookup. * **Compositional reasoning metadata** — `structural`, `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 | Split | Rows | Distinct images | | --------------- | ------ | --------------- | | `testdev.lance` | 12,578 | 398 | 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 | Column | Type | Notes | | ----------------------------- | ------------------------------- | ------------------------------------------------------------------- | | `id` | `int64` | Row index within split | | `image` | `large_binary` | Inline JPEG bytes (duplicated across rows that share an `image_id`) | | `image_id` | `string` | GQA scene-graph image id | | `question_id` | `string` | GQA question id | | `question` | `string` | Compositional natural-language question | | `answers` | `list` | One-element list (the GQA short answer) | | `answer` | `string` | Canonical short answer (used for FTS) | | `full_answer` | `string?` | Full-sentence answer | | `structural` | `string?` | One of `verify`, `query`, `compare`, `choose`, `logical` | | `semantic` | `string?` | One of `attr`, `cat`, `global`, `obj`, `rel` | | `detailed` | `string?` | Fine-grained type (e.g. `weatherVerifyC`) | | `is_balanced` | `bool` | GQA balanced subset flag | | `group_global`, `group_local` | `string?` | GQA reasoning-group ids | | `semantic_str` | `string?` | Compact description of the reasoning program | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | | `question_emb` | `fixed_size_list` | 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > 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`. ## Search 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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_id`s, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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](https://lance.org/guide/data_evolution/). ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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`](https://huggingface.co/datasets/lmms-lab/GQA). GQA is released under [CC BY 4.0](https://creativecommons.org/licenses/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} } ``` # Handwriting OCR Source: https://docs.lancedb.com/datasets/handwriting-ocr This Lance-formatted version of the Doctor's Handwritten Prescription BD dataset contains 4,680 cropped PNG images of handwritten medicine names from Bangladesh. Each row keeps the original image bytes with the medicine and generic-name labels… Source dataset card and downloadable files for `lance-format/handwriting-ocr`. This Lance-formatted version of the [Doctor's Handwritten Prescription BD dataset](https://www.kaggle.com/datasets/mamun1113/doctors-handwritten-prescription-bd-dataset) contains 4,680 cropped PNG images of handwritten medicine names from Bangladesh. Each row keeps the original image bytes with the medicine and generic-name labels, plus deterministic search metadata derived from those labels. The dataset contains three source-preserved splits: train, validation, and test. > \[!NOTE] > **Training note:** The same samples appear repeatedly because a model (during training) should learn from multiple *different handwritten crops* of the same medicine, because they capture different writers, pen strokes, sizes, and image quality. The dataset would need to be shuffled and sampled appropriately before running downstream training tasks ## Key features * **Original inline PNG bytes** in `image`, with no image folders or sidecar files required. * **Three preserved splits**: 3,120 training, 780 validation, and 780 test examples. * **Medicine annotations**: 78 medicine names mapped to 15 generic names. * **Built-in text retrieval** over `searchable_summary`, with scalar indices matching the companion OCR retrieval project. ## Splits | Split | Rows | Source directory | | ------------ | ----: | ---------------- | | `train` | 3,120 | `Training` | | `validation` | 780 | `Validation` | | `test` | 780 | `Testing` | ## Schema | Column | Type | Notes | | -------------------- | -------------- | ------------------------------------------------------------------ | | `id` | `string` | Stable split-qualified row identifier | | `image` | `large_binary` | Original inline PNG bytes | | `medicine_name` | `string` | Source medicine-name label | | `generic_name` | `string` | Source generic-name label | | `normalized_text` | `string` | Deterministic normalization of `medicine_name` | | `category` | `string` | `medication` for every source row | | `is_medical` | `bool` | `true` for every source row | | `needs_human_review` | `bool` | `false`; this conversion does not run OCR confidence assessment | | `searchable_summary` | `string` | Label-derived text: `Medication mention: ()` | | `source_dataset` | `string` | Upstream Kaggle dataset identifier | | `split` | `string` | Original source split name: `Training`, `Validation`, or `Testing` | | `image_filename` | `string` | Original PNG filename within the source split | `normalized_text`, `category`, `is_medical`, `needs_human_review`, and `searchable_summary` are deterministic conversion metadata. They are not OCR or clinical-model predictions. ## Pre-built indices * `INVERTED` (FTS) on `searchable_summary` * `BTREE` on `id` * `BITMAP` on `category` * `BITMAP` on `needs_human_review` ## 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 through the standard Hugging Face `datasets` interface when you want a streaming sample or your pipeline already uses `Dataset` / `IterableDataset`. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset( "lance-format/handwriting-ocr", split="train", streaming=True, ) for row in hf_ds.take(3): print(row["medicine_name"], row["generic_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/docs)), and is the interface most users interact with. It wraps the downloaded dataset as a queryable table with search and filter builders, and is the entry point used by the Search, Curate, Evolve, and Versioning examples below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./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 lower-level APIs. Use it to inspect the schema, fragments, and pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("./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 repeated random access and training are faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/handwriting-ocr --repo-type dataset --local-dir ./handwriting-ocr > ``` > > Then run the examples below from `./handwriting-ocr`, where `./data` contains the downloaded Lance splits. ## Search The bundled full-text index makes medicine and generic-name lookups efficient without an embedding model. `searchable_summary` combines the two labels, so a text query can return the matching word image and its annotations in one bounded result set. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./data") tbl = db.open_table("train") hits = ( tbl.search("Paracetamol") .select(["id", "medicine_name", "generic_name", "searchable_summary"]) .limit(10) .to_list() ) for row in hits: print(row["medicine_name"], "-", row["generic_name"]) ``` The FTS index operates on label-derived text, not a transcription produced from the image. It is useful for inspecting the handwritten examples associated with known medicine or generic names. ## Curate Filters produce small, explicit candidate sets for experiments. The example selects a bounded set of Paracetamol examples while avoiding image bytes until a later step needs them. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./data") tbl = db.open_table("train") candidates = ( tbl.search() .where("generic_name = 'Paracetamol'") .select(["id", "image_filename", "medicine_name", "generic_name"]) .limit(100) .to_list() ) ``` This list can be reviewed directly, saved as an experiment manifest, or used as the input to an annotation or training workflow. ## Evolve Lance can append new columns without rewriting the original images or annotations. For example, a local copy can add a flag for a specific generic name using a SQL expression, then merge independently produced labels by `id`. > **Note**: Mutations require a local copy. The examples in this card assume the dataset was downloaded into the current directory, as shown above. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb import pyarrow as pa db = lancedb.connect("./data") tbl = db.open_table("train") tbl.add_columns({"is_paracetamol": "generic_name = 'Paracetamol'"}) review_labels = pa.table({ "id": ["train_00000"], "review_status": ["reviewed"], }) tbl.merge(review_labels, on="id") ``` The source columns and pre-built indices remain intact. New labels become available to queries as soon as the write commits. ## Train Projection lets a training loop read only the columns it needs. The image bytes and medicine label below are projected through a standard PyTorch `DataLoader`; fields added later do not add read cost unless selected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "medicine_name"]) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) for batch in loader: # Decode PNG bytes and train an OCR or classification model. ... ``` This keeps the original image representation available for training while avoiding unrelated columns on each epoch. ## Versioning Every Lance mutation creates a version, so the table history can identify the exact split and annotation state used by a retrieval service or experiment. The downloaded dataset can list its history and create tags for reproducible local workflows. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./data") tbl = db.open_table("train") print("Current version:", tbl.version) print("History:", tbl.list_versions()) print("Tags:", tbl.tags.list()) tbl.tags.create("labels-v1", tbl.version) ``` A pinned version or tag keeps retrieval and training tied to a known dataset state even as review labels or derived columns are added later. ## Materialize a subset Use a filtered query to materialize a compact local LanceDB table for writes or intensive training. The query streams matching rows and projected columns without materializing the full result set in Python memory. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb source_db = lancedb.connect("./data") source_tbl = source_db.open_table("train") batches = ( source_tbl.search() .where("generic_name = 'Paracetamol'") .select(["id", "image", "medicine_name", "generic_name", "searchable_summary"]) .to_batches() ) local_db = lancedb.connect("./paracetamol-subset") local_db.create_table("train", batches) ``` The resulting local table can replace `./data` in the Evolve, Train, and Versioning examples when a compact writable subset is sufficient. ## Citation Please cite both the upstream dataset and the associated research paper: ```bibtex theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @inproceedings{mia2024deep, title={A Deep Neural Network Approach with Pioneering Local Dataset to Recognize Doctor's Handwritten Prescription in Bangladesh}, author={Mia, Abdur Rahim and Chowdhury, Mohammad Abdullah-Al-Sajid and Mamun, Abdullah Al and Ruddra, Aurunave Mollik and Tanny, Nawshin Tabassum}, booktitle={2024 International Conference on Advances in Computing, Communication, Electrical, and Smart Systems}, pages={1--6}, year={2024}, publisher={IEEE}, doi={10.1109/iCACCESS61735.2024.10499631} } ``` Source dataset: [Mamun1113, Doctor's Handwritten Prescription BD dataset](https://www.kaggle.com/datasets/mamun1113/doctors-handwritten-prescription-bd-dataset). Associated paper: [IEEE Xplore](https://ieeexplore.ieee.org/document/10499631). ## License The upstream Kaggle metadata identifies the database license as [Open Database License (ODbL) v1.0](https://opendatacommons.org/licenses/odbl/1.0/) and states that the contents are copyright the original authors. The upstream page also describes the dataset as free for educational and research use. Downstream users should review the upstream dataset page and publication, satisfy the ODbL obligations, and obtain any additional permissions needed for their intended use. # HotpotQA distractor Source: https://docs.lancedb.com/datasets/hotpotqa-distractor A Lance-formatted version of HotpotQA using the distractor config — multi-hop reading-comprehension questions where each answer requires combining facts from two Wikipedia paragraphs, with 10 candidate paragraphs per question (gold + 8… Source dataset card and downloadable files for `lance-format/hotpotqa-distractor-lance`. A Lance-formatted version of [HotpotQA](https://hotpotqa.github.io/) using the `distractor` config — multi-hop reading-comprehension questions where each answer requires combining facts from two Wikipedia paragraphs, with 10 candidate paragraphs per question (gold + 8 distractors). The dataset ships with MiniLM question embeddings, flattened context text for full-text search, and pre-built ANN/FTS indices, available directly from the Hub at `hf://datasets/lance-format/hotpotqa-distractor-lance/data`. ## Key features * **Multi-hop questions with gold supporting facts** — each row carries the question, the canonical short answer, and the `(title, sent_id)` pointers into the paragraphs that justify it. * **Ten candidate paragraphs per question** in the parallel `context_titles` / `context_sentences` columns, plus a flattened `context_text` field that feeds the FTS index. * **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 lookup. * **One columnar dataset** — scan metadata cheaply, then read the heavy context text only for the rows you actually want. ## Splits | Split | Rows | | ------------------ | ------ | | `train.lance` | 90,447 | | `validation.lance` | 7,405 | ## Schema | Column | Type | Notes | | ---------------------- | ------------------------------- | -------------------------------------------- | | `id` | `string` | HotpotQA question id | | `question` | `string` | The question | | `answer` | `string` | Reference short answer (`yes` / `no` / span) | | `type` | `string?` | `bridge` or `comparison` | | `level` | `string?` | `easy` / `medium` / `hard` | | `supporting_titles` | `list` | Wikipedia titles that contain the gold facts | | `supporting_sent_ids` | `list` | Sentence indices into those titles | | `context_titles` | `list` | All 10 paragraph titles (gold + distractors) | | `context_sentences` | `list>` | Sentences per paragraph | | `context_text` | `string` | Flattened paragraphs — feeds the FTS index | | `num_supporting_facts` | `int32` | Number of gold supporting facts | | `question_emb` | `fixed_size_list` | MiniLM question embedding | ## Pre-built indices * `IVF_PQ` on `question_emb` — semantic question lookup (cosine) * `INVERTED` (FTS) on `question` and `context_text` — keyword and hybrid search * `BTREE` on `id`, `answer` — stable lookup by identifier * `BITMAP` on `type`, `level` — cheap predicate evaluation for question class ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/hotpotqa-distractor-lance", split="validation", 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](https://lancedb.com/docs)), and is the interface most users interact with. Each `.lance` file in `data/` is a table — open by name (`train`, `validation`). The same handle is used by the Search, Curate, Evolve, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/hotpotqa-distractor-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/hotpotqa-distractor-lance --repo-type dataset --local-dir ./hotpotqa-distractor-lance > ``` > > Then point Lance or LanceDB at `./hotpotqa-distractor-lance/data`. ## Search The bundled `IVF_PQ` index on `question_emb` makes nearest-neighbour question lookup a single call. In production you would encode an incoming user question through the same 384-dim MiniLM encoder used at ingest and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in so the snippet works without loading a model. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-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("level = 'hard'", prefilter=True) .select(["question", "answer", "supporting_titles", "type"]) .limit(10) .to_list() ) for r in hits: print(f"[{r['type']}] {r['question']} -> {r['answer']}") ``` The result set carries only the projected columns; the 384-d `question_emb` is never read on the result side, and the long `context_text` body is left untouched, keeping the working set small even when the underlying scan touches every row of the train split. Because the dataset also ships an `INVERTED` index on both `question` and `context_text`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query against the full paragraph text. LanceDB merges the two result lists and reranks them in a single call, which is useful when a named entity must literally appear in one of the supporting paragraphs but the dense side still does most of the ranking. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["question_emb"]) .text("inception dunkirk") .select(["question", "answer", "supporting_titles"]) .limit(10) .to_list() ) for r in hybrid_hits: print(r["question"], "->", r["answer"]) ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency for your workload. ## Curate Building a focused evaluation slice usually means stacking predicates over the question metadata before any context text gets read. Lance evaluates the filter inside a single scan, so the candidate set comes back already filtered, and the bounded `.limit(2000)` keeps the output small enough to inspect. The example below assembles a set of hard, multi-hop comparison questions for which the gold answer is a real span rather than `yes`/`no`. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where( "type = 'comparison' " "AND level = 'hard' " "AND num_supporting_facts >= 2 " "AND answer NOT IN ('yes', 'no') " "AND length(question) >= 40", prefilter=True, ) .select(["id", "question", "answer", "supporting_titles"]) .limit(2000) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['question']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of question ids, or hand to the Evolve and Train sections below. Neither `context_text` nor `context_sentences` is read by this scan, so a 2000-row curation pass against the Hub moves only kilobytes of metadata. ## 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` column and a `is_multi_hop` flag, 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 corpus. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./hotpotqa-distractor-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "question_length": "length(question)", "is_multi_hop": "num_supporting_facts >= 2", }) ``` If the values you want to attach already live in another table (offline retriever scores, reranker logits, alternate embeddings from a stronger model), merge them in by joining on the question `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa retriever_scores = pa.table({ "id": pa.array(["5a8b57f25542995d1e6f1371", "5a8c7595554299585d9e36b6"]), "bm25_top1_score": pa.array([12.7, 9.4]), }) tbl.merge(retriever_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 encoder over the question text), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 multi-hop QA model the natural projection is the question plus the flattened context and the gold answer; for a question-encoder retraining loop the precomputed embedding is enough on its own. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["question", "context_text", "answer"]) loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; tokenize, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["question_emb", "answer"]` to `select_columns(...)` on the next run reads only the 384-d vectors and the short answer string, which is the right shape for fine-tuning a retrieval head 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./hotpotqa-distractor-lance/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("hard-multihop-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="hard-multihop-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A QA system locked to `hard-multihop-v1` keeps returning stable supporting facts while the dataset evolves in parallel — newly added retriever 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/hotpotqa-distractor-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where( "type = 'comparison' " "AND level = 'hard' " "AND num_supporting_facts >= 2" ) .select(["id", "question", "answer", "supporting_titles", "context_text", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./hotpotqa-hard-comparison") local_db.create_table("train", batches) ``` The resulting `./hotpotqa-hard-comparison` 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/hotpotqa-distractor-lance/data` for `./hotpotqa-hard-comparison`. ## Source & license Converted from [`hotpot_qa`](https://huggingface.co/datasets/hotpot_qa) (`distractor` config). HotpotQA is released under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). ## Citation ``` @inproceedings{yang2018hotpotqa, title={HotpotQA: A Dataset for Diverse, Explainable Multi-hop Question Answering}, author={Yang, Zhilin and Qi, Peng and Zhang, Saizheng and Bengio, Yoshua and Cohen, William W. and Salakhutdinov, Ruslan and Manning, Christopher D.}, booktitle={Empirical Methods in Natural Language Processing (EMNLP)}, year={2018} } ``` # ImageNet-1k Validation Source: https://docs.lancedb.com/datasets/imagenet-1k-val A Lance-formatted version of the canonical 50,000-image ImageNet-1k (ILSVRC2012) validation split, sourced from benjamin-paine/imagenet-1k. Each row is one image with its integer class id, a string class name, and a cosine-normalized OpenCLIP image… Source dataset card and downloadable files for `lance-format/imagenet-1k-val-lance`. A Lance-formatted version of the canonical 50,000-image ImageNet-1k (ILSVRC2012) validation split, sourced from [`benjamin-paine/imagenet-1k`](https://huggingface.co/datasets/benjamin-paine/imagenet-1k). Each row is one image with its integer class id, a string class name, and a cosine-normalized OpenCLIP image embedding — all stored inline and available directly from the Hub at `hf://datasets/lance-format/imagenet-1k-val-lance/data`. The 1.28 M ImageNet-1k train split (\~155 GB) is intentionally out of scope for this redistribution; the val split is the canonical evaluation slice for classification benchmarks and is small enough (\~7 GB Lance) to ride entirely in inline storage alongside its embeddings. ## Key features * **Inline JPEG bytes** in the `image` column — no per-class folders, no sidecar files. * **Pre-computed OpenCLIP image embeddings** (`image_emb`, ViT-B/32 trained on `laion2b_s34b_b79k`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for similarity search. * **Both label representations** — integer `label` (0-999) and string `label_name` (first synonym of the WordNet synset, e.g. `golden_retriever`) — with scalar indices on both for fast class filters. * **One columnar dataset** — scan labels and embeddings cheaply, fetch image bytes only for the rows you actually need. ## Splits A single split, shipped as `validation.lance` (50,000 rows). ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | ----------------------------------------------------------------------------- | | `id` | `int64` | Row index within the split, 0-49,999 (natural join key) | | `image` | `large_binary` | Inline JPEG bytes | | `label` | `int32` | Class id (0-999) | | `label_name` | `string` | First synonym of the synset, underscore-spaced (e.g. `golden_retriever`) | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` / `laion2b_s34b_b79k` image embedding (cosine-normalized) | The full comma-separated WordNet synset descriptions for each class are stored in the dataset metadata under `lance:class_names`. ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine, `num_partitions=64`) * `BTREE` on `label` — fast equality / range filters by class id * `BITMAP` on `label_name` — fast set-membership filters by class name ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/imagenet-1k-val-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") tbl = db.open_table("validation") 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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/imagenet-1k-val-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/imagenet-1k-val-lance --repo-type dataset --local-dir ./imagenet-1k-val-lance > ``` > > Then point Lance or LanceDB at `./imagenet-1k-val-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes nearest-neighbor retrieval over the validation set a single call. In production you would encode a query image through the same OpenCLIP `ViT-B-32` / `laion2b_s34b_b79k` model used at ingest (cosine-normalized) and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding already stored in row 42 as a runnable stand-in, so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") tbl = db.open_table("validation") seed = ( tbl.search() .select(["image_emb", "label_name"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label_name"]) .limit(10) .to_list() ) print(f"reference class: {seed['label_name']}") for r in hits: print(f" id={r['id']:>6} {r['label_name']}") ``` Because the embeddings are cosine-normalized at ingest, `metric="cosine"` is the right choice and the first hit will typically be the seed image itself — a useful sanity check. Tune `nprobes` and `refine_factor` to trade recall against latency for your workload. ## Curate A typical curation pass for an ImageNet-style classification or robustness study narrows the validation set to a single class (or a synset prefix) and then materializes a small candidate set for inspection. Stacking the filter and the projection inside a single scan keeps the result small and explicit, and the bounded `.limit(200)` makes it cheap to inspect before committing the subset to anything downstream. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where("label_name = 'golden_retriever'") .select(["id", "label", "label_name"]) .limit(200) .to_list() ) print(f"{len(candidates)} golden_retriever validation rows") ``` The `BITMAP` index on `label_name` resolves the predicate without scanning, and the `image` column is never read, so the network traffic for the candidate scan is dominated by the small metadata payload rather than JPEG bytes. The result is a plain list of dictionaries, ready to inspect, persist as a manifest of row ids, or feed into the Evolve and Train workflows below. To grab a family of related classes, replace the equality with a `LIKE` predicate such as `label_name LIKE 'tabby%'` or an `IN` set over a curated synset list. ## 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 coarse `is_dog` flag over a curated set of canine synsets, which can then be used directly in later `where` clauses without re-listing the class set 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./imagenet-1k-val-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "is_dog": "label_name IN ('golden_retriever', 'Labrador_retriever', 'beagle', 'pug', 'poodle')", }) ``` If the values you want to attach already live in another table — per-class hypernyms from WordNet, ImageNet-A / ImageNet-R membership flags, model-prediction logs from an external eval run — merge them in by joining on `label_name`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa hypernyms = pa.table({ "label_name": pa.array(["golden_retriever", "tabby", "espresso"]), "hypernym": pa.array(["dog", "cat", "beverage"]), }) tbl.merge(hypernyms, on="label_name") ``` 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 an alternative vision backbone over the image bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## Train Projection lets a training loop — or, more commonly for this split, an evaluation 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. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") tbl = db.open_table("validation") eval_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(eval_ds, batch_size=128, shuffle=False, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the JPEG bytes, forward through your classifier, accumulate top-1 / top-5... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` 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 training a linear probe or a lightweight classifier head on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./imagenet-1k-val-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("clip-vitb32-laion2b-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="clip-vitb32-laion2b-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. An evaluation harness locked to `clip-vitb32-laion2b-v1` keeps reporting numbers against a fixed snapshot of labels and embeddings even as the dataset evolves in parallel; newly added columns or relabelings do not change what the tag resolves to. A research experiment pinned to the same tag can be rerun later against the exact same images, 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 or evaluation 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/imagenet-1k-val-lance/data") remote_tbl = remote_db.open_table("validation") batches = ( remote_tbl.search() .where("label_name IN ('golden_retriever', 'Labrador_retriever', 'beagle', 'pug', 'poodle')") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./imagenet-dogs-subset") local_db.create_table("validation", batches) ``` The resulting `./imagenet-dogs-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/imagenet-1k-val-lance/data` for `./imagenet-dogs-subset`. ## Source & license Converted from [`benjamin-paine/imagenet-1k`](https://huggingface.co/datasets/benjamin-paine/imagenet-1k), itself a redistribution of the [ILSVRC2012 ImageNet-1k validation split](https://image-net.org/challenges/LSVRC/2012/). All use is subject to the [ImageNet terms of access](https://image-net.org/download.php) — **for research use only**. ## Citation ``` @inproceedings{deng2009imagenet, title={ImageNet: A Large-Scale Hierarchical Image Database}, author={Deng, Jia and Dong, Wei and Socher, Richard and Li, Li-Jia and Li, Kai and Fei-Fei, Li}, booktitle={IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2009} } ``` # Datasets Source: https://docs.lancedb.com/datasets/index Browse Lance-format datasets ready to query directly from the Hugging Face Hub. The [`lance-format`](https://huggingface.co/lance-format) organization on Hugging Face publishes a growing catalog of multimodal datasets in Lance format. Each one bundles the raw data (images, audio, video, or text), pre-computed embeddings, and on-disk vector / full-text indices as first-class columns in the same dataset — so vector search, full-text search, and filtered scans work directly via `hf://` URIs without downloading. This is powered under the hood by the [Lance format's native Hugging Face integration](https://lance.org/integrations/huggingface/) (via the [`pylance`](https://pypi.org/project/pylance/) library). LanceDB sits on top of Lance and gives you a convenient table-style interface to query these datasets straight from the Hub: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format//data") tbl = db.open_table("train") # Vector search, full-text search, or filtered scans — directly on the Hub results = tbl.search(query).limit(10).to_list() ``` Click any card below for usage examples, schema, and pre-built indices. For a complete walkthrough of the integration itself, see the [Hugging Face Hub integration page](/integrations/ai/huggingface). ## Image Classification `lance-format/mnist-lance` — A Lance-formatted version of the classic MNIST handwritten-digit dataset covering 70,000 28×28 grayscale digits across ten balanced classes. Each row carries inline PNG bytes, the digit label, the human-readable class name, and a cosine-normalized… `lance-format/cifar10-lance` — A Lance-formatted version of CIFAR-10 covering 60,000 32×32 RGB images across ten balanced object classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image embedding, all backed… `lance-format/fashion-mnist-lance` — A Lance-formatted version of Fashion-MNIST covering 70,000 28×28 grayscale clothing images across ten balanced apparel classes. Each row carries inline PNG bytes, the integer label, the human-readable class name, and a cosine-normalized CLIP image… `lance-format/food101-lance` — A Lance-formatted version of Food-101, the fine-grained dish-classification benchmark of 101,000 photos spread evenly across 101 dish classes, sourced from ethz/food101. Each row carries the inline JPEG bytes, the integer label, the human-readable… `lance-format/oxford-pets-lance` — A Lance-formatted version of the Oxford-IIIT Pet dataset — 7,390 cat and dog photos across 37 breeds — sourced from pcuenq/oxford-pets. Each row carries the inline JPEG bytes, the breed name, a species flag distinguishing cats from dogs, and a… `lance-format/stanford-cars-lance` — A Lance-formatted version of the Stanford Cars fine-grained benchmark — 8,144 photographs across 196 make/model/year classes — sourced from Multimodal-Fatima/StanfordCars\_train. Each row carries the inline JPEG bytes, the integer class id, a… `lance-format/imagenet-1k-val-lance` — A Lance-formatted version of the canonical 50,000-image ImageNet-1k (ILSVRC2012) validation split, sourced from benjamin-paine/imagenet-1k. Each row is one image with its integer class id, a string class name, and a cosine-normalized OpenCLIP image… `lance-format/eurosat-lance` — A Lance-formatted version of EuroSAT, the canonical Sentinel-2 RGB land-cover benchmark, sourced from blanchon/EuroSAT\_RGB. Each row is a single 64×64 RGB tile with its integer class id, the human-readable class name, and a cosine-normalized… ## OCR `lance-format/handwriting-ocr` — This Lance-formatted version of the Doctor's Handwritten Prescription BD dataset contains 4,680 cropped PNG images of handwritten medicine names from Bangladesh. Each row keeps the original image bytes with the medicine and generic-name labels… ## Object Detection & Segmentation `lance-format/coco-detection-2017-lance` — A Lance-formatted version of the COCO 2017 object detection benchmark, sourced from detection-datasets/coco. Each row is one image with its inline JPEG bytes, the full per-image list of bounding boxes, COCO 80-class category ids and names… `lance-format/pascal-voc-2012-segmentation-lance` — A Lance-formatted version of the Pascal VOC 2012 semantic segmentation split, sourced from nateraw/pascal-voc-2012. Each row pairs an inline JPEG image with the per-pixel PNG segmentation mask and a cosine-normalized OpenCLIP ViT-B-32 image… `lance-format/ade20k-lance` — A Lance-formatted version of the full ADE20K scene parsing benchmark, sourced from 1aurent/ADE20K. Each row is one scene image with its inline JPEG bytes, a per-pixel semantic segmentation map encoded as PNG bytes, an optional instance map, scene… `lance-format/kitti-2d-detection-lance` — A Lance-formatted version of the KITTI 2D Object Detection benchmark, sourced from nateraw/kitti so no manual signup or download from cvlibs.net is required. Each row is a single driving frame with inline JPEG bytes, the full set of 2D and 3D… ## Image Retrieval `lance-format/coco-captions-2017-lance` — A Lance-formatted version of the COCO Captions 2017 corpus, redistributed via lmms-lab/COCO-Caption2017. Each row is one image with 5–7 human-written captions, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of… `lance-format/flickr30k-lance` — A Lance-formatted version of Flickr30k, redistributed via lmms-lab/flickr30k. Each row is one image with 5 human-written captions, a cosine-normalized CLIP image embedding, and a cosine-normalized CLIP text embedding of the canonical caption — all… `lance-format/laion-1m` — A Lance-formatted slice of the LAION image-text corpus (\~1M rows) with inline JPEG bytes, CLIP image embeddings (img\_emb), full metadata, and a pre-built ANN index — all available directly from the Hub at… ## Visual Question Answering `lance-format/chartqa-lance` — A Lance-formatted version of ChartQA, a benchmark for question answering over scientific and business charts that demands a mix of logical and visual reasoning, redistributed via lmms-lab/ChartQA. Each row carries the chart image as inline JPEG… `lance-format/docvqa-lance` — A Lance-formatted version of DocVQA, a benchmark for visual question answering over document images such as industry and government scans, multi-page reports, forms, and receipts, redistributed via lmms-lab/DocVQA (DocVQA config). Each row carries… `lance-format/textvqa-lance` — A Lance-formatted version of TextVQA — visual question answering where the question requires reading text in the image (street signs, product labels, screen captures) — sourced from lmms-lab/textvqa. Each row carries the image bytes, the question… `lance-format/vqav2-lance` — A Lance-formatted version of VQAv2 — open-ended visual question answering on COCO images — sourced from lmms-lab/VQAv2. Each row is one (image, question, 10 annotator answers) triple with paired CLIP image and question embeddings drawn from the… `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… ## Text QA `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… `lance-format/trivia-qa-lance` — A Lance-formatted version of TriviaQA (rc.nocontext config) — a large reading-comprehension dataset of trivia questions paired with a canonical answer, accepted aliases, and entity-type metadata — with MiniLM question embeddings stored inline and… `lance-format/hotpotqa-distractor-lance` — A Lance-formatted version of HotpotQA using the distractor config — multi-hop reading-comprehension questions where each answer requires combining facts from two Wikipedia paragraphs, with 10 candidate paragraphs per question (gold + 8… `lance-format/natural-questions-val-lance` — A Lance-formatted version of the Natural Questions validation split — 7,830 real Google search queries paired with the full Wikipedia article a human used to answer them, plus 1–5 annotator labels per question. MiniLM question embeddings are stored… `lance-format/ms-marco-v2.1-lance` — A Lance-formatted version of MS MARCO v2.1 — Microsoft's machine-reading-comprehension benchmark built from anonymized Bing query logs. Each row is one user query, the up-to-10 candidate passages Bing retrieved for it with relevance flags, and the… ## Text Corpora `lance-format/fineweb-edu` — A Lance-formatted version of FineWeb-Edu — over 1.5 billion educational web passages with cleaned text, source metadata, language detection signals, and 384-dim text embeddings — available directly from the Hub at… ## Speech `lance-format/librispeech-clean-lance` — A Lance-formatted version of the LibriSpeech ASR clean configuration, sourced from openslr/librispeech\_asr. Each row is one utterance with inline FLAC audio bytes, the reference transcript, a sentence-transformers embedding of that transcript, and… ## Video `lance-format/openvid-lance` — A Lance-formatted version of the OpenVid-1M corpus — 937,957 high-quality clips with inline MP4 bytes, 1024-dim video embeddings, captions, and rich per-clip quality signals — available directly from the Hub at… ## Robotics `lance-format/lerobot-pusht-lance` — A Lance-formatted version of lerobot/pusht — the canonical PushT benchmark from the Diffusion Policy paper — packaged using the same three-table layout as lance-format/lerobot-xvla-soft-fold so consumers can flip between datasets without changing… `lance-format/lerobot-xvla-soft-fold` — A Lance-formatted version of lerobot/xvla-soft-fold — a multi-camera robotics dataset from the X-VLA project — packaged as three Lance tables for efficient frame-level training, episode-level trajectory loading, and direct access to the original… ## Share your own dataset Got a multimodal dataset you want to publish? Convert it to Lance and push it to the Hub! Anyone who opens it gets vector search, full-text search, and filtered scans on the data out of the box, without recreating the embeddings or indexes on their end. A step-by-step walkthrough on the LanceDB blog covering CLI setup, packaging your dataset, pushing to your namespace, and writing a dataset card. Or browse the [latest trending Lance datasets](https://huggingface.co/datasets?format=format:lance\&sort=trending) on Hugging Face. # KITTI 2D Detection Source: https://docs.lancedb.com/datasets/kitti-2d-detection A Lance-formatted version of the KITTI 2D Object Detection benchmark, sourced from nateraw/kitti so no manual signup or download from cvlibs.net is required. Each row is a single driving frame with inline JPEG bytes, the full set of 2D and 3D… Source dataset card and downloadable files for `lance-format/kitti-2d-detection-lance`. A Lance-formatted version of the [KITTI 2D Object Detection benchmark](https://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=2d), sourced from [`nateraw/kitti`](https://huggingface.co/datasets/nateraw/kitti) so no manual signup or download from cvlibs.net is required. Each row is a single driving frame with inline JPEG bytes, the full set of 2D and 3D object annotations stored as parallel per-object lists, plus a cosine-normalized OpenCLIP `ViT-B-32` image embedding — all available directly from the Hub at `hf://datasets/lance-format/kitti-2d-detection-lance/data`. KITTI is the canonical autonomous-driving detection benchmark with 8 object classes drawn from real street scenes around Karlsruhe. It is widely used for AV perception research and serves as a small-scale companion to nuScenes and Waymo. ## Key features * **Inline JPEG bytes** in the `image` column — no parallel `image_2/` and `label_2/` folders to keep in sync. * **Per-object 2D and 3D annotations on the same row** — bounding boxes, observation angles, 3D dimensions, locations, yaw, occlusion and truncation flags travel as parallel list columns of equal length. * **Pre-computed CLIP image embeddings** (`image_emb`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for visual similarity over driving scenes. * **Scalar and label-list indices** on `num_objects` and `types_present` make per-class and crowdedness filters cheap on the Hub copy and locally. ## Splits | Split | Rows | Notes | | ------------- | ----- | --------------------------------------- | | `train.lance` | 7,481 | Official KITTI training set with labels | The KITTI `test` split has no public labels and is intentionally not bundled. Add it via `--splits train test` in `kitti/dataprep.py` if you want the unlabeled images for inference. ## Schema | Column | Type | Notes | | --------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | `int64` | Row index within split (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes (re-encoded from the source PNG) | | `bboxes` | `list>` | 2D box per object — `[left, top, right, bottom]` in pixel coords | | `alphas` | `list` | Observation angle per object (radians, KITTI convention) | | `dimensions` | `list>` | 3D box `(h, w, l)` per object, in metres | | `locations` | `list>` | 3D centre `(x, y, z)` per object in camera coords, in metres | | `rotation_y` | `list` | Yaw per object in camera coords (radians) | | `occluded` | `list` | KITTI occlusion flag (0=visible, 1=partly, 2=largely, 3=unknown) | | `truncated` | `list` | Truncation fraction per object (0.0-1.0) | | `types` | `list` | Class name per object (`Car`, `Van`, `Truck`, `Pedestrian`, `Person_sitting`, `Cyclist`, `Tram`, `Misc`, `DontCare`) | | `num_objects` | `int32` | Number of annotated objects in the frame | | `types_present` | `list` | Deduped class names — feeds the LABEL\_LIST index | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | All `list<...>` annotation columns on the same row are aligned — index `i` across `bboxes`, `alphas`, `dimensions`, `locations`, `rotation_y`, `occluded`, `truncated`, and `types` describes the same physical object. ## Pre-built indices * `IVF_PQ` on `image_emb` — `metric=cosine` * `BTREE` on `num_objects` * `LABEL_LIST` on `types_present` ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/kitti-2d-detection-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["id"], row["num_objects"], row["types_present"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/kitti-2d-detection-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/kitti-2d-detection-lance --repo-type dataset --local-dir ./kitti-2d-detection-lance > ``` > > Then point Lance or LanceDB at `./kitti-2d-detection-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes visual nearest-neighbour search over driving scenes a single call. In production you would encode a query frame (or a scene prototype) through OpenCLIP `ViT-B-32` at runtime and pass the resulting 512-d cosine-normalized vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "types_present"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "num_objects", "types_present"]) .limit(10) .to_list() ) print("query scene types:", seed["types_present"]) for r in hits: print(f" id={r['id']:>5} n={r['num_objects']:>2} {r['types_present']}") ``` Because the embeddings are cosine-normalized, `metric="cosine"` is the natural choice and the first hit is typically the seed row itself. Visual neighbours tend to share scene-level structure (highway vs. urban intersection vs. parked-cars row) before they share class composition, which is what makes the cross between `image_emb` and the `types_present` / `num_objects` indices useful for the curation patterns below. ## Curate KITTI's parallel per-object list columns make composition-based filters natural: pick scenes by which classes are present, by how many objects are in them, or by the occlusion profile of those objects. Lance evaluates these predicates inside a single filtered scan, and the bounded `.limit(...)` keeps the candidate set small and explicit. The first snippet below finds crowded scenes that contain at least one cyclist and one pedestrian — a useful slice for vulnerable-road-user studies. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-lance/data") tbl = db.open_table("train") vru = ( tbl.search() .where( "array_has_all(types_present, ['Cyclist', 'Pedestrian']) AND num_objects >= 8", prefilter=True, ) .select(["id", "num_objects", "types_present"]) .limit(200) .to_list() ) print(f"{len(vru)} VRU-rich frames") ``` A second pass can combine a structural filter with visual similarity: take a crowded urban seed frame and look for visually similar frames whose object lists also contain cars. This is a one-shot retrieval against the `IVF_PQ` index, joined with the `LABEL_LIST` index on `types_present` inside a single query. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} seed = ( tbl.search() .where("num_objects >= 10 AND array_contains(types_present, 'Car')", prefilter=True) .select(["image_emb"]) .limit(1) .to_list()[0] ) similar_crowded = ( tbl.search(seed["image_emb"]) .metric("cosine") .where("array_contains(types_present, 'Car')", prefilter=True) .select(["id", "num_objects", "types_present"]) .limit(50) .to_list() ) ``` The results are plain lists of dictionaries, ready to inspect, persist as manifests of `id`s, or feed into the Evolve and Train workflows below. The annotation list columns and `image_emb` are read; the JPEG bytes are not touched until you ask for them. ## 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 per-frame counts for the two most safety-relevant classes plus a `has_vru` flag, all 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./kitti-2d-detection-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "num_cars": "array_length(array_filter(types, x -> x = 'Car'))", "num_pedestrians": "array_length(array_filter(types, x -> x = 'Pedestrian'))", "has_vru": "array_has_any(types_present, ['Pedestrian', 'Cyclist'])", }) ``` If the values you want to attach already live in another table — detector predictions on the same frames, LIDAR-derived per-frame features, or human re-annotation — merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "pred_num_cars": pa.array([3, 5, 0], type=pa.int32()), }) tbl.merge(predictions, 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 (running a fresh detector over the JPEG bytes, deriving alternative embeddings), Lance also provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 2D detector, project the JPEG bytes together with the per-object `bboxes` and `types` lists; everything else (3D annotations, CLIP embeddings) stays on disk until you opt in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-lance/data") tbl = db.open_table("train") train_ds = ( Permutation.identity(tbl) .select_columns(["image", "bboxes", "types"]) ) loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; 3D fields and image_emb stay on disk. # decode the JPEGs, drop DontCare boxes, build target tensors, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "types_present"]` to `select_columns(...)` on the next run skips JPEG decoding entirely and reads only the cached 512-d vectors plus the deduped class list, which is the right shape for training a lightweight scene classifier or a linear probe on top of frozen CLIP features. ## Versioning Every mutation to a Lance dataset, whether it adds a column, merges predictions, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./kitti-2d-detection-lance/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("kitti-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="kitti-clip-vitb32-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A perception service locked to `kitti-clip-vitb32-v1` keeps returning stable retrieval results while the dataset evolves in parallel — newly added detector predictions or alternative embeddings do not change what the tag resolves to. A detection-training experiment pinned to the same tag can be rerun later against the exact same frames and annotations, 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. The filter below carves out a vulnerable-road-user training set — frames that contain at least one pedestrian or cyclist — and writes them to a local LanceDB database. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/kitti-2d-detection-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("array_has_any(types_present, ['Pedestrian', 'Cyclist'])") .select(["id", "image", "bboxes", "types", "num_objects", "types_present", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./kitti-vru-subset") local_db.create_table("train", batches) ``` The resulting `./kitti-vru-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/kitti-2d-detection-lance/data` for `./kitti-vru-subset`. ## Source & license Converted from [`nateraw/kitti`](https://huggingface.co/datasets/nateraw/kitti). KITTI is released under the [CC BY-NC-SA 3.0 license](https://creativecommons.org/licenses/by-nc-sa/3.0/) by Karlsruhe Institute of Technology and Toyota Technological Institute at Chicago — **non-commercial research use only**. See the [KITTI license page](https://www.cvlibs.net/datasets/kitti/) for details. ## Citation ``` @inproceedings{geiger2012are, title={Are we ready for autonomous driving? The KITTI vision benchmark suite}, author={Geiger, Andreas and Lenz, Philip and Urtasun, Raquel}, booktitle={IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2012} } ``` # LAION-1M Source: https://docs.lancedb.com/datasets/laion-1m A Lance-formatted slice of the LAION image-text corpus (~1M rows) with inline JPEG bytes, CLIP image embeddings (img_emb), full metadata, and a pre-built ANN index — all available directly from the Hub at… Source dataset card and downloadable files for `lance-format/laion-1m`. A Lance-formatted slice of the [LAION](https://laion.ai/blog/laion-5b/) image-text corpus (\~1M rows) with inline JPEG bytes, CLIP image embeddings (`img_emb`), full metadata, and a pre-built ANN index — all available directly from the Hub at `hf://datasets/lance-format/laion-1m/data/train.lance`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (`img_emb`, 768-dim) with a bundled `IVF_PQ` index for similarity search. * **Full LAION metadata** — captions, URLs, NSFW flags, EXIF, dimensions, similarity scores. * **One columnar dataset** — scan metadata cheaply, then fetch image bytes only for the rows you want. ## Splits `train.lance` ## Schema | Column | Type | Notes | | ---------------------------------------- | ------------------------------- | ------------------------------------- | | `key` | `int64` | Row key (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes | | `image_path` | `string` | Original filename | | `caption` | `string` | Image caption | | `url` | `string` | Source URL | | `NSFW` | `int64` | 0 = safe, 1 = NSFW | | `LICENSE` | `string` | Per-row license tag | | `similarity` | `float64` | CLIP image–text cosine similarity | | `width`, `height` | `int64` | Image dimensions | | `original_width`, `original_height` | `int64` | Original dimensions before resize | | `exif`, `md5`, `status`, `error_message` | `string` | Provenance / metadata | | `img_emb` | `fixed_size_list` | CLIP image embedding | ## Pre-built indices * `IVF_PQ` on `img_emb` — vector similarity search (L2) ## 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 if your pipeline already speaks `Dataset` / `IterableDataset` or you want a quick streaming sample without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/laion-1m", split="train", streaming=True) for row in hf_ds.take(3): print(row["caption"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, and Versioning examples below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/laion-1m/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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/laion-1m/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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/laion-1m --repo-type dataset --local-dir ./laion-1m > ``` > > Then point Lance or LanceDB at `./laion-1m/data`. ## Search The bundled `IVF_PQ` index on `img_emb` makes approximate-nearest-neighbor search a single call. In production you would encode a user prompt or query image through CLIP at runtime and pass the resulting 768-d vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/laion-1m/data") tbl = db.open_table("train") query = ( tbl.search() .select(["img_emb"]) .limit(1) .offset(42) .to_list()[0]["img_emb"] ) hits = ( tbl.search(query) .metric("L2") .select(["caption", "url", "similarity"]) .limit(10) .to_list() ) for r in hits: print(f"{r['similarity']:.3f} | {r['caption'][:80]}") ``` Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency for your workload. ## Curate Building a focused subset usually means combining similarity with metadata filters. Lance evaluates both inside a single query, so the candidate set comes back already filtered. The example below finds images visually similar to a seed row and restricts the result to safe-rated, high-resolution rows in one call. The bounded `.limit(500)` keeps the output small enough to inspect or hand off. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/laion-1m/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["img_emb", "caption"]) .limit(1) .offset(42) .to_list()[0] ) candidates = ( tbl.search(seed["img_emb"]) .where('"NSFW" = 0 AND similarity > 0.3 AND width >= 512', prefilter=True) .select(["key", "url", "caption", "similarity"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates around: {seed['caption'][:60]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of row keys, or feed into the Evolve and Train workflows below. ## 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 precomputed `aspect_ratio` and an `is_high_res` flag, 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 corpus. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./laion-1m/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "aspect_ratio": "CAST(width AS DOUBLE) / CAST(height AS DOUBLE)", "is_high_res": "width >= 512 AND height >= 512", }) ``` If the values you want to attach already live in another table (offline labels, classifier predictions, aesthetic scores), merge them in by joining on the `key` column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa labels = pa.table({ "key": pa.array([0, 1, 2]), "aesthetic_score": pa.array([7.1, 6.4, 8.9]), }) tbl.merge(labels, on="key") ``` 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 an embedding model over the image bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/laion-1m/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "caption"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; img_emb / img_emb_dinov3 stay on disk. # decode the JPEG bytes, tokenize the captions, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["img_emb_dinov3", "caption"]` to `select_columns(...)` on the next run reads only those columns, with no data movement or shard reorganization. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/laion-1m/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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./laion-1m/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("aesthetic-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="aesthetic-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A retrieval system locked to `aesthetic-v1` keeps returning stable results while the dataset evolves in parallel; newly added columns 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 data, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/laion-1m/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where('"NSFW" = 0 AND similarity > 0.35 AND width >= 512') .select(["key", "image", "caption", "url", "img_emb"]) .to_batches() ) local_db = lancedb.connect("./laion-subset") local_db.create_table("train", batches) ``` The resulting `./laion-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/laion-1m/data` for `./laion-subset`. ## Citation ``` @article{schuhmann2022laion5b, title={LAION-5B: An open large-scale dataset for training next generation image-text models}, author={Schuhmann, Christoph and others}, journal={NeurIPS Datasets and Benchmarks Track}, year={2022} } ``` ## License Content inherits LAION's original licensing and safety guidelines. Review [LAION policy](https://laion.ai/blog/laion-5b/) before downstream use. # LeRobot PushT Source: https://docs.lancedb.com/datasets/lerobot-pusht A Lance-formatted version of lerobot/pusht — the canonical PushT benchmark from the Diffusion Policy paper — packaged using the same three-table layout as lance-format/lerobot-xvla-soft-fold so consumers can flip between datasets without changing… Source dataset card and downloadable files for `lance-format/lerobot-pusht-lance`. A Lance-formatted version of [`lerobot/pusht`](https://huggingface.co/datasets/lerobot/pusht) — the canonical PushT benchmark from the [Diffusion Policy paper](https://diffusion-policy.cs.columbia.edu/) — packaged using the same three-table layout as [`lance-format/lerobot-xvla-soft-fold`](https://huggingface.co/datasets/lance-format/lerobot-xvla-soft-fold) so consumers can flip between datasets without changing code. Available directly from the Hub at `hf://datasets/lance-format/lerobot-pusht-lance/data`. ## Key features * **Three-table layout** — `frames`, `episodes`, `videos` — so frame-level training, episode-level trajectory work, and raw video access live side-by-side without scattered parquet shards or sidecar MP4 directories. * **Inline MP4 segments** in `episodes.lance` (one blob per camera, with `from_timestamp` / `to_timestamp` bounds) and full source MP4s in `videos.lance`, all surfaced as lazy `BlobFile` handles via `take_blobs` so metadata scans never read the bytes. * **Frame-level observations and actions** in `frames.lance` with stable `episode_index`, `frame_index`, and `index` columns for joining or temporal iteration. * **Schema-evolution friendly** — add alternate camera streams, language annotations, or model predictions later without rewriting the data. ## Tables | Table | Rows \~ | Purpose | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------ | | `frames.lance` | one row per frame | Per-frame observations, actions, episode/task indices | | `episodes.lance` | one row per episode | Full per-episode trajectories plus per-camera MP4 segment blobs and timestamp bounds | | `videos.lance` | one row per source MP4 | Raw source video blobs and file-level provenance (path, size, sha256) | Use `frames.lance` for low-level training (loss-per-timestep, state-conditioned policies). Use `episodes.lance` when you need the full trajectory and the matching video segments together. Use `videos.lance` when you want direct access to the original encoded video files. ## Schemas ### `frames.lance` | Column | Type | Notes | | ------------------- | --------------- | ----------------------------------- | | `observation_state` | `list` | Robot state vector for that frame | | `action` | `list` | Action vector for that frame | | `timestamp` | `float` | Canonical frame timestamp (seconds) | | `frame_index` | `int64` | Frame index within episode | | `episode_index` | `int64` | Parent episode id | | `index` | `int64` | Global frame index | | `task_index` | `int64` | Task id | ### `episodes.lance` | Column | Type | Notes | | ------------------------- | ----------------------------- | ---------------------------------------------------------------- | | `episode_index` | `int64` | Episode id | | `task_index` | `int64` | Task id | | `fps` | `int32` | Frame rate of the episode video segments | | `timestamps` | `list` | Per-frame timestamps | | `actions` | `list>` | Per-frame action vectors | | `observation_state` | `list>` | Per-frame robot state vectors | | `_video_blob` | `large_binary` (blob-encoded) | Inline MP4 segment for each camera, read lazily via `take_blobs` | | `_from_timestamp` | `float64` | Segment start time | | `_to_timestamp` | `float64` | Segment end time | ### `videos.lance` | Column | Type | Notes | | --------------------------- | ----------------------------- | ------------------------------- | | `camera_angle` | `string` | Camera key | | `chunk_index`, `file_index` | `int32` | IDs parsed from the source path | | `relative_path`, `filename` | `string` | Provenance | | `file_size_bytes` | `int64` | Source MP4 size | | `sha256` | `string` | SHA256 of the MP4 bytes | | `video_blob` | `large_binary` (blob-encoded) | Raw source MP4 bytes | ## Pre-built indices None bundled. Build indices on a local copy if a workload calls for them — e.g., a `BTREE` on `frames.episode_index` for fast episode lookup, or a vector index after attaching observation embeddings via Evolve. ## 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. Each Lance table is a separate `datasets` config. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/lerobot-pusht-lance", split="frames", streaming=True) for row in hf_ds.take(3): print(row["episode_index"], row["frame_index"], row["action"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/docs)), and is the interface most users interact with. Each `.lance` file in `data/` is a table — open by name. The same handles are used by the Search, Curate, Evolve, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") frames = db.open_table("frames") episodes = db.open_table("episodes") videos = db.open_table("videos") print(len(frames), len(episodes), len(videos)) ``` ## 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 — or when you need the blob-level `take_blobs` entry point that streams MP4 bytes lazily from inline storage. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/lerobot-pusht-lance/data/frames.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 to video segments and any kind of indexed search are dramatically faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/lerobot-pusht-lance --repo-type dataset --local-dir ./lerobot-pusht > ``` > > Then point Lance or LanceDB at `./lerobot-pusht/data`. ## Search PushT does not ship a vector index out of the box — observation states are low-dimensional and most robotics workflows look up by index rather than by similarity. The bundled identifier columns (`episode_index`, `task_index`, `frame_index`) make exact lookups a single filtered scan. The example below pulls the first few frames of episode 0 from the frames table. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") frames = db.open_table("frames") slice_ = ( frames.search() .where("episode_index = 0 AND frame_index < 10", prefilter=True) .select(["episode_index", "frame_index", "timestamp", "action", "observation_state"]) .limit(10) .to_list() ) for r in slice_: print(r["frame_index"], r["timestamp"], r["action"]) ``` For similarity-style search across states or actions, attach an embedding column via Evolve and build an `IVF_PQ` index on it (see Evolve below). For visual similarity over rendered frames, the pre-extracted-frames pattern in Train below produces a table that can carry a learned image embedding alongside the pixels. ## Curate A typical curation pass for a robotics workflow starts with an episode-level filter — pick episodes with a particular task, length, or initial condition — and then drops down to the frames within those episodes. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(...)` makes it cheap to inspect. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") episodes = db.open_table("episodes") frames = db.open_table("frames") # Pick a handful of episodes for the default task. ep_rows = ( episodes.search() .where("task_index = 0", prefilter=True) .select(["episode_index", "fps", "observation_images_image_from_timestamp", "observation_images_image_to_timestamp"]) .limit(10) .with_row_id(True) .to_list() ) ep_ids = [r["episode_index"] for r in ep_rows] # Pull the frames belonging to those episodes for the next stage. frame_rows = ( frames.search() .where(f"episode_index IN ({', '.join(map(str, ep_ids))})", prefilter=True) .select(["episode_index", "frame_index", "timestamp", "action", "observation_state"]) .limit(2000) .to_list() ) print(f"{len(ep_rows)} episodes, {len(frame_rows)} frames selected") ``` Neither scan reads any video bytes. The MP4 segments live in the blob-encoded `_video_blob` columns and stay on disk until something explicitly asks for them. ## 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 `action_magnitude` and a `large_action` flag to the frames table, either of which can then be used directly in `where` clauses. > **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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./lerobot-pusht/data") # local copy required for writes frames = db.open_table("frames") frames.add_columns({ "action_magnitude": "SQRT(action[1] * action[1] + action[2] * action[2])", "large_action": "SQRT(action[1] * action[1] + action[2] * action[2]) > 5.0", }) ``` If the values you want to attach already live in another table (offline reward labels, classifier predictions, learned observation embeddings), merge them in by joining on the appropriate key — `index` for frames or `episode_index` for episodes: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa rewards = pa.table({ "index": pa.array([0, 1, 2]), "reward_to_go": pa.array([1.4, 1.3, 1.2]), }) frames.merge(rewards, on="index") ``` The original columns and the inline video blobs are untouched, so existing code that does not reference the new columns continues to work unchanged. For column values that require a Python computation (e.g., running a visual encoder over the decoded video frames), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## Train A common pattern for vision-conditioned policy training is to pre-extract decoded frame pixels once into a derived LanceDB table — one row per frame, with the per-frame `action` and `observation_state` already joined in — and train against that table with the regular projection-based dataloader. `take_blobs` is the mechanism that makes the extraction step tractable: each episode's MP4 segment is randomly addressable in `episodes.lance` (the `from_timestamp` / `to_timestamp` columns give the segment bounds), so the pass can subset bytes on demand and write decoded frames into a fresh table without an external file store. Other workflows project the `_video_blob` columns from `episodes.lance` directly and decode at the batch boundary, or skip pixels entirely and train a state-only policy on `frames.lance` — the right shape is workload-specific. The actual training loop is the same `Permutation.identity(tbl).select_columns(...)` snippet in every case; only the source table and the column list change. For a state-only policy, the frames table is already in the right shape — no pre-extraction needed: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") frames = db.open_table("frames") train_ds = Permutation.identity(frames).select_columns(["observation_state", "action"]) loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4) ``` For a vision-conditioned policy, train against a pre-extracted frames-with-pixels table that joins each frame's decoded image to its `action` and `observation_state`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./lerobot-pusht-frames") # local table produced by the one-time extraction tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "observation_state", "action"]) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) ``` The inline `_video_blob` storage and `take_blobs` still earn their place outside of the training loop — visualizing an episode in a notebook, sampling for human review, one-off evaluation against a held-out task, and the pre-extraction step itself — but they are not the dataloader. ## Versioning Every mutation to a Lance table, whether it adds a column, merges labels, or builds an index, commits a new version. Each of `frames`, `episodes`, and `videos` is versioned independently, so a column added to `frames` does not bump the version of `episodes`. You can list versions and inspect the history directly from the Hub copy; creating new tags requires a local copy since tags are writes. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") frames = db.open_table("frames") print("frames version:", frames.version) print("history:", frames.list_versions()) print("tags:", frames.tags.list()) ``` Once you have a local copy, tag the table for reproducibility: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./lerobot-pusht/data") local_frames = local_db.open_table("frames") local_frames.tags.create("pusht-v1", local_frames.version) ``` Reopen by tag or by version number against either the Hub copy or a local one: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} frames_v1 = db.open_table("frames", version="pusht-v1") frames_v5 = db.open_table("frames", version=5) ``` Pinning supports two workflows. A policy locked to `pusht-v1` keeps reproducing the same behavior while the dataset evolves in parallel. A training experiment pinned to the same tag can be rerun later against the exact same frames, so changes in metrics reflect model changes rather than data drift. ## 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, index builds) need a writable backing store, and a training pipeline benefits from a local copy with fast random access into the video blobs. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/lerobot-pusht-lance/data") remote_frames = remote_db.open_table("frames") batches = ( remote_frames.search() .where("task_index = 0 AND episode_index < 50") .select(["episode_index", "frame_index", "index", "timestamp", "action", "observation_state"]) .to_batches() ) local_db = lancedb.connect("./pusht-task0-subset") local_db.create_table("frames", batches) ``` The resulting `./pusht-task0-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/lerobot-pusht-lance/data` for `./pusht-task0-subset`. The same pattern applies to `episodes` and `videos` — narrow each table to the rows your workload needs, and the resulting database stays small enough to index and iterate cheaply. ## Source & license Converted from [`lerobot/pusht`](https://huggingface.co/datasets/lerobot/pusht) (LeRobot v3.0 dataset format). PushT is released under the Apache 2.0 license by the LeRobot project and the Diffusion Policy authors. ## Citation ``` @misc{cadene2024lerobot, title={LeRobot: State-of-the-art Machine Learning for Real-World Robotics in PyTorch}, author={R{\'e}mi Cadene and Simon Alibert and Alexander Soare and Quentin Gallou{\'e}dec and Adil Zouitine and Steven Palma and Pepijn Kooijmans and Michel Aractingi and Mustafa Shukor and Martino Russi and Francesco Capuano and Caroline Pascal and Jade Choghari and Jess Moss and Thomas Wolf}, year={2024}, url={https://github.com/huggingface/lerobot} } @inproceedings{chi2023diffusion, title={Diffusion Policy: Visuomotor Policy Learning via Action Diffusion}, author={Chi, Cheng and Feng, Siyuan and Du, Yilun and Xu, Zhenjia and Cousineau, Eric and Burchfiel, Benjamin and Song, Shuran}, booktitle={Robotics: Science and Systems}, year={2023} } ``` # LeRobot X-VLA Soft-Fold Source: https://docs.lancedb.com/datasets/lerobot-xvla-soft-fold A Lance-formatted version of lerobot/xvla-soft-fold — a multi-camera robotics dataset from the X-VLA project — packaged as three Lance tables for efficient frame-level training, episode-level trajectory loading, and direct access to the original… Source dataset card and downloadable files for `lance-format/lerobot-xvla-soft-fold`. A Lance-formatted version of [`lerobot/xvla-soft-fold`](https://huggingface.co/datasets/lerobot/xvla-soft-fold) — a multi-camera robotics dataset from the [X-VLA](https://thu-air-dream.github.io/X-VLA/) project — packaged as three Lance tables for efficient frame-level training, episode-level trajectory loading, and direct access to the original encoded videos. Available directly from the Hub at `hf://datasets/lance-format/lerobot-xvla-soft-fold/data`. * **1,542 episodes** * **2,852,512 frames** at **20 FPS** * **3 camera streams per episode** — `cam_high`, `cam_left_wrist`, `cam_right_wrist` * **Robot state and action vectors** aligned to frame timestamps ## Key features * **Three-table layout** — `frames`, `episodes`, `videos` — so frame-level training, episode-level trajectory work, and raw video access live side-by-side without scattered parquet shards or sidecar MP4 directories. * **Per-camera inline MP4 segments** in `episodes.lance`, with `from_timestamp` / `to_timestamp` bounds per camera and per episode, surfaced as lazy `BlobFile` handles via `take_blobs` so metadata scans never read the bytes. * **Frame-level observations and actions** in `frames.lance` with stable `episode_index`, `frame_index`, and `index` columns for joining or temporal iteration. * **Source MP4 provenance** in `videos.lance` (`relative_path`, `filename`, `file_size_bytes`, `sha256`) alongside the raw bytes, for integrity checks or custom decode pipelines. ## Tables | Table | Rows | Purpose | | ---------------- | --------- | ------------------------------------------------------------------------------------ | | `frames.lance` | 2,852,512 | Per-frame observations, actions, episode/task indices | | `episodes.lance` | 1,542 | Full per-episode trajectories plus per-camera MP4 segment blobs and timestamp bounds | | `videos.lance` | 104 | Raw source MP4 files (one row per source MP4) with file-level provenance | Use `frames.lance` for low-level training (loss-per-timestep, state-conditioned policies). Use `episodes.lance` when you need the full trajectory and the matching per-camera video segments together. Use `videos.lance` when you want direct access to the original encoded video files. ## Schemas ### `frames.lance` | Column | Type | Notes | | ------------------- | --------------- | ----------------------------------- | | `observation_state` | `list` | Robot state vector for that frame | | `action` | `list` | Action vector for that frame | | `time_stamp` | `float` | Original source timestamp field | | `timestamp` | `float` | Canonical frame timestamp (seconds) | | `frame_index` | `int64` | Frame index within episode | | `episode_index` | `int64` | Parent episode id | | `index` | `int64` | Global frame index | | `task_index` | `int64` | Task id | ### `episodes.lance` | Column | Type | Notes | | --------------------------------------------------- | ----------------------------- | ---------------------------------------- | | `episode_index` | `int64` | Episode id | | `task_index` | `int64` | Task id | | `fps` | `int32` | Frame rate of the episode video segments | | `timestamps` | `list` | Per-frame timestamps | | `actions` | `list>` | Per-frame action vectors | | `observation_state` | `list>` | Per-frame robot state vectors | | `observation_images_cam_high_video_blob` | `large_binary` (blob-encoded) | Inline MP4 segment for `cam_high` | | `observation_images_cam_high_from_timestamp` | `float64` | `cam_high` segment start time | | `observation_images_cam_high_to_timestamp` | `float64` | `cam_high` segment end time | | `observation_images_cam_left_wrist_video_blob` | `large_binary` (blob-encoded) | Inline MP4 segment for `cam_left_wrist` | | `observation_images_cam_left_wrist_from_timestamp` | `float64` | `cam_left_wrist` segment start time | | `observation_images_cam_left_wrist_to_timestamp` | `float64` | `cam_left_wrist` segment end time | | `observation_images_cam_right_wrist_video_blob` | `large_binary` (blob-encoded) | Inline MP4 segment for `cam_right_wrist` | | `observation_images_cam_right_wrist_from_timestamp` | `float64` | `cam_right_wrist` segment start time | | `observation_images_cam_right_wrist_to_timestamp` | `float64` | `cam_right_wrist` segment end time | ### `videos.lance` | Column | Type | Notes | | --------------------------- | ----------------------------- | ------------------------------- | | `camera_angle` | `string` | Camera key (e.g. `cam_high`) | | `chunk_index`, `file_index` | `int32` | IDs parsed from the source path | | `relative_path`, `filename` | `string` | Provenance | | `file_size_bytes` | `int64` | Source MP4 size | | `sha256` | `string` | SHA256 of the MP4 bytes | | `video_blob` | `large_binary` (blob-encoded) | Raw source MP4 bytes | ## Pre-built indices None bundled. Build indices on a local copy if a workload calls for them — e.g., a `BTREE` on `frames.episode_index` for fast per-episode lookup, or a vector index after attaching observation embeddings via Evolve. ## 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. Each Lance table is a separate `datasets` config. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/lerobot-xvla-soft-fold", split="frames", streaming=True) for row in hf_ds.take(3): print(row["episode_index"], row["frame_index"], row["action"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/docs)), and is the interface most users interact with. Each `.lance` file in `data/` is a table — open by name. The same handles are used by the Search, Curate, Evolve, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") frames = db.open_table("frames") episodes = db.open_table("episodes") videos = db.open_table("videos") print(len(frames), len(episodes), len(videos)) ``` ## 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 — or when you need the blob-level `take_blobs` entry point that streams MP4 bytes lazily from inline storage. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/lerobot-xvla-soft-fold/data/frames.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 to video segments and any kind of indexed search are dramatically faster against a local copy. The full dataset is **>50 GB**, so ensure you have sufficient disk space: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/lerobot-xvla-soft-fold --repo-type dataset --local-dir ./lerobot-xvla-soft-fold > ``` > > Then point Lance or LanceDB at `./lerobot-xvla-soft-fold/data`. For most workflows, the Materialize-a-subset section at the end of this card is a better starting point than downloading the full corpus. ## Search This dataset does not ship a vector index out of the box — observation states are low-dimensional and most robotics workflows look up by index rather than by similarity. The bundled identifier columns (`episode_index`, `task_index`, `frame_index`) make exact lookups a single filtered scan. The example below pulls the first few frames of episode 30 from the frames table. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") frames = db.open_table("frames") slice_ = ( frames.search() .where("episode_index = 30 AND frame_index < 10", prefilter=True) .select(["episode_index", "frame_index", "timestamp", "action", "observation_state"]) .limit(10) .to_list() ) for r in slice_: print(r["frame_index"], r["timestamp"], r["action"]) ``` For similarity-style search across states or actions, attach an embedding column via Evolve and build an `IVF_PQ` index on it. For visual similarity over rendered frames, the pre-extracted-frames pattern in Train below produces a table that can carry a learned image embedding alongside the pixels. ## Curate A typical curation pass for a robotics workflow starts with an episode-level filter — pick episodes with a particular task, length, or initial condition — and then either iterates frames or pulls the matching video segments. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(...)` makes it cheap to inspect. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") episodes = db.open_table("episodes") ep_rows = ( episodes.search() .where("task_index = 0 AND fps = 20", prefilter=True) .select([ "episode_index", "observation_images_cam_high_from_timestamp", "observation_images_cam_high_to_timestamp", ]) .limit(20) .with_row_id(True) .to_list() ) print(f"{len(ep_rows)} episodes selected") for r in ep_rows[:3]: print( f" ep {r['episode_index']} " f"{r['observation_images_cam_high_from_timestamp']:.2f}s → " f"{r['observation_images_cam_high_to_timestamp']:.2f}s" ) ``` Neither this scan nor any of the per-camera segment columns are read. The MP4 segments live in the blob-encoded `_video_blob` columns and stay on disk until something explicitly asks for them — which makes "find me the right episodes" a metadata-only operation against a multi-million-frame corpus. ## 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 `episode_duration` column to the episodes table from the existing `cam_high` timestamp bounds. > **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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./lerobot-xvla-soft-fold/data") # local copy required for writes episodes = db.open_table("episodes") episodes.add_columns({ "episode_duration_s": ( "observation_images_cam_high_to_timestamp - " "observation_images_cam_high_from_timestamp" ), "is_long_episode": ( "(observation_images_cam_high_to_timestamp - " " observation_images_cam_high_from_timestamp) > 120.0" ), }) ``` If the values you want to attach already live in another table (offline reward labels, classifier predictions, learned observation embeddings), merge them in by joining on the appropriate key — `index` for frames or `episode_index` for episodes: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa ep_labels = pa.table({ "episode_index": pa.array([0, 1, 2]), "outcome": pa.array(["success", "partial", "success"]), }) episodes.merge(ep_labels, on="episode_index") ``` The original columns and the inline video blobs are untouched, so existing code that does not reference the new columns continues to work unchanged. For column values that require a Python computation (e.g., running a visual encoder over the decoded video frames), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## Train A common pattern for vision-language-action training is to pre-extract decoded frame pixels once into a derived LanceDB table — one row per frame, with the per-frame `action` and `observation_state` already joined in, and one column per camera holding the decoded image — and train against that table with the regular projection-based dataloader. `take_blobs` is the mechanism that makes the extraction step tractable: each episode's per-camera MP4 segment is randomly addressable in `episodes.lance` (the `*_from_timestamp` / `*_to_timestamp` columns give the segment bounds), so the pass can subset bytes on demand and write decoded frames into a fresh table without an external file store. Other workflows project the `*_video_blob` columns from `episodes.lance` directly and decode at the batch boundary, or skip pixels entirely and train a state-only policy on `frames.lance` — the right shape is workload-specific. The actual training loop is the same `Permutation.identity(tbl).select_columns(...)` snippet in every case; only the source table and the column list change. For a state-only policy, the frames table is already in the right shape — no pre-extraction needed: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") frames = db.open_table("frames") train_ds = Permutation.identity(frames).select_columns(["observation_state", "action"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) ``` For a vision-language-action policy, train against a pre-extracted frames-with-pixels table that joins each frame's three decoded camera images to its `action` and `observation_state`. Picking the cameras the model actually conditions on is then a column projection — `cam_high` alone, all three, or any subset: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./lerobot-xvla-frames") # local table produced by the one-time extraction tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns( ["cam_high", "cam_left_wrist", "cam_right_wrist", "observation_state", "action"] ) loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) ``` The inline `_video_blob` storage and `take_blobs` still earn their place outside of the training loop — visualizing an episode in a notebook, sampling for human review, one-off evaluation, and the pre-extraction step itself — but they are not the dataloader. ## Versioning Every mutation to a Lance table, whether it adds a column, merges labels, or builds an index, commits a new version. Each of `frames`, `episodes`, and `videos` is versioned independently, so a column added to `frames` does not bump the version of `episodes`. You can list versions and inspect the history directly from the Hub copy; creating new tags requires a local copy since tags are writes. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") frames = db.open_table("frames") print("frames version:", frames.version) print("history:", frames.list_versions()) print("tags:", frames.tags.list()) ``` Once you have a local copy, tag the table for reproducibility: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./lerobot-xvla-soft-fold/data") local_frames = local_db.open_table("frames") local_frames.tags.create("xvla-v1", local_frames.version) ``` Reopen by tag or by version number against either the Hub copy or a local one: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} frames_v1 = db.open_table("frames", version="xvla-v1") frames_v5 = db.open_table("frames", version=5) ``` Pinning supports two workflows. A policy locked to `xvla-v1` keeps reproducing the same behavior while the dataset evolves in parallel. A training experiment pinned to the same tag can be rerun later against the exact same frames and segments, so changes in metrics reflect model changes rather than data drift. ## Materialize a subset At >50 GB across three tables and millions of frames, few workflows want the full corpus on local disk. The practical entry point 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 — including the per-camera `_video_blob` columns on `episodes.lance`, which stream through Arrow record batches rather than being assembled in a single buffer. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/lerobot-xvla-soft-fold/data") remote_episodes = remote_db.open_table("episodes") batches = ( remote_episodes.search() .where("task_index = 0 AND episode_index < 50") .select([ "episode_index", "task_index", "fps", "timestamps", "actions", "observation_state", "observation_images_cam_high_video_blob", "observation_images_cam_high_from_timestamp", "observation_images_cam_high_to_timestamp", ]) .to_batches() ) local_db = lancedb.connect("./xvla-task0-subset") local_db.create_table("episodes", batches) ``` The resulting `./xvla-task0-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/lerobot-xvla-soft-fold/data` for `./xvla-task0-subset`. The same pattern applies to `frames` and `videos` — narrow each table to the rows your workload needs, and the resulting database stays small enough to index and iterate cheaply. ## Source & license Converted from [`lerobot/xvla-soft-fold`](https://huggingface.co/datasets/lerobot/xvla-soft-fold) (LeRobot v3.0 dataset format), originally released as part of the [X-VLA](https://thu-air-dream.github.io/X-VLA/) project. Apache 2.0. ## Citation ``` @article{zheng2025xvla, title={X-VLA: Soft-Prompted Transformer as Scalable Cross-Embodiment Vision-Language-Action Model}, author={Zheng and others}, journal={arXiv preprint arXiv:2510.10274}, year={2025} } @misc{cadene2024lerobot, title={LeRobot: State-of-the-art Machine Learning for Real-World Robotics in PyTorch}, author={R{\'e}mi Cadene and Simon Alibert and Alexander Soare and Quentin Gallou{\'e}dec and Adil Zouitine and Steven Palma and Pepijn Kooijmans and Michel Aractingi and Mustafa Shukor and Martino Russi and Francesco Capuano and Caroline Pascal and Jade Choghari and Jess Moss and Thomas Wolf}, year={2024}, url={https://github.com/huggingface/lerobot} } ``` # LibriSpeech clean Source: https://docs.lancedb.com/datasets/librispeech-clean A Lance-formatted version of the LibriSpeech ASR clean configuration, sourced from openslr/librispeech_asr. Each row is one utterance with inline FLAC audio bytes, the reference transcript, a sentence-transformers embedding of that transcript, and… Source dataset card and downloadable files for `lance-format/librispeech-clean-lance`. A Lance-formatted version of the LibriSpeech ASR `clean` configuration, sourced from [`openslr/librispeech_asr`](https://huggingface.co/datasets/openslr/librispeech_asr). Each row is one utterance with inline FLAC audio bytes, the reference transcript, a sentence-transformers embedding of that transcript, and speaker/chapter metadata — all available directly from the Hub at `hf://datasets/lance-format/librispeech-clean-lance/data`. ## Key features * **Inline FLAC bytes** in the `audio` column at 16 kHz mono, with no re-encoding from the upstream parquet. * **Sentence-transformers embedding of the transcript** in `text_emb` (`all-MiniLM-L6-v2`, 384-dim, cosine-normalized) with a bundled `IVF_PQ` index for semantic transcript search. * **Pre-built `INVERTED` FTS index on `text`** and `BTREE` indices on `id`, `speaker_id`, and `chapter_id` for keyword search and stable lookup by identifier. * **Per-utterance metadata** — `speaker_id`, `chapter_id`, `num_chars`, `sampling_rate` — that downstream filters can stack on. ## Splits | Split | Source config | Rows | Description | | ----------------------- | ----------------- | ------ | ------------------------------ | | `dev_clean.lance` | `dev.clean` | 2,703 | Standard ASR validation set | | `test_clean.lance` | `test.clean` | 2,620 | Standard ASR test set | | `train_clean_100.lance` | `train.clean.100` | 28,539 | 100-hour clean training subset | > The 360-hour and 500-hour LibriSpeech subsets (`train.360`, `train.other.500`) are not bundled here. To extend, point `librispeech/dataprep.py` at additional splits. ## Schema | Column | Type | Notes | | --------------- | ------------------------------- | ------------------------------------------------------------ | | `id` | `string` | Utterance id (e.g. `1272-128104-0000`) | | `audio` | `large_binary` | Inline FLAC bytes (16 kHz mono) | | `sampling_rate` | `int32` | Always 16,000 | | `text` | `string` | Reference transcript | | `speaker_id` | `int64` | LibriVox speaker id | | `chapter_id` | `int64` | LibriVox chapter id | | `num_chars` | `int32` | Length of `text` in characters | | `text_emb` | `fixed_size_list` | sentence-transformers `all-MiniLM-L6-v2` (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `text_emb` — semantic transcript search (cosine) * `INVERTED` (FTS) on `text` — keyword and hybrid search * `BTREE` on `id`, `speaker_id`, `chapter_id` — fast lookup by identifier ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/librispeech-clean-lance", split="test_clean", streaming=True) for row in hf_ds.take(3): print(row["id"], row["text"][:80]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/docs)), and is the interface most users interact with. Each `.lance` file in `data/` is a table — open by name (`dev_clean`, `test_clean`, `train_clean_100`). The same handle is used by the Search, Curate, Evolve, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") tbl = db.open_table("train_clean_100") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/librispeech-clean-lance/data/train_clean_100.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, ANN search, and audio decoding are far faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/librispeech-clean-lance --repo-type dataset --local-dir ./librispeech-clean > ``` > > Then point Lance or LanceDB at `./librispeech-clean/data`. ## Search The bundled `IVF_PQ` index on `text_emb` makes semantic transcript retrieval a single call. In production you would encode a query string through the same sentence-transformers model used at ingest (`all-MiniLM-L6-v2`, cosine-normalized), then pass the resulting 384-d vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") tbl = db.open_table("train_clean_100") seed = ( tbl.search() .select(["text_emb", "text"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["text_emb"], vector_column_name="text_emb") .metric("cosine") .select(["id", "speaker_id", "text"]) .limit(10) .to_list() ) print("query transcript:", seed["text"][:80]) for r in hits: print(f" {r['id']} spk={r['speaker_id']} {r['text'][:80]}") ``` The `audio` blob is never touched. A top-10 semantic search moves a few kilobytes of transcript text rather than the FLAC bytes for every candidate. Because the dataset also ships an `INVERTED` index on `text`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query — useful when a name or domain term must literally appear in the transcript but you still want the semantic side to rank the rest. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="text_emb") .vector(seed["text_emb"]) .text("astronomy") .select(["id", "speaker_id", "text"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['id']} spk={r['speaker_id']} {r['text'][:80]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate Building a focused subset of utterances usually means combining content with structure — pick utterances by a single speaker, or above a minimum transcript length, or matching a topic. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(500)` makes it cheap to inspect. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") tbl = db.open_table("train_clean_100") candidates = ( tbl.search() .where("speaker_id = 1272 AND num_chars >= 60", prefilter=True) .select(["id", "chapter_id", "num_chars", "text"]) .limit(500) .with_row_id(True) .to_list() ) print(f"{len(candidates)} utterances; first: {candidates[0]['text'][:80]}") ``` The scan never reads the `audio` column. Lance stores binary columns independently, so a metadata-only curation pass moves only the transcript text and scalar fields across the wire — even though the underlying table includes hours of inline FLAC audio. ## 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 `is_long_utterance` flag and a coarse `length_bucket`, either of which can then be used directly in `where` clauses without re-evaluating 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./librispeech-clean/data") # local copy required for writes tbl = db.open_table("train_clean_100") tbl.add_columns({ "is_long_utterance": "num_chars >= 200", "length_bucket": ( "CASE WHEN num_chars < 80 THEN 'short' " "WHEN num_chars < 200 THEN 'medium' ELSE 'long' END" ), }) ``` If the values you want to attach already live in another table (alternate transcripts, speaker embeddings, model predictions), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array(["1272-128104-0000", "1272-128104-0001"]), "wer": pa.array([0.04, 0.12]), }) tbl.merge(predictions, on="id") ``` The original columns and indices are untouched, so existing code that does not reference the new columns continues to work unchanged. For column values that require a Python computation (e.g., running a speaker embedding model over the FLAC bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## Train A common pattern for audio training is to pre-extract decoded features once into a derived LanceDB table — one row per training-ready window of log-mel frames or raw PCM samples — and train against that table with the regular projection-based dataloader. `take_blobs` is the mechanism that makes the extraction step tractable: each utterance's FLAC bytes are randomly addressable, so the pass can subset audio on demand and write decoded windows into a fresh table without an external file store. Other workflows project `audio` directly through `select_columns(...)` and decode at the batch boundary, or skip audio entirely and train on the cached transcript embeddings — the right shape is workload-specific. The actual training loop is the same `Permutation.identity(tbl).select_columns(...)` snippet in every case; only the source table and the column list change. Against a pre-extracted features table: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./librispeech-features") # local table produced by the one-time extraction tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["log_mel", "text", "speaker_id"]) loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) ``` Against the cached transcript embeddings on the source table (no audio decode): ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader src_db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") src_tbl = src_db.open_table("train_clean_100") train_ds = Permutation.identity(src_tbl).select_columns(["text_emb", "speaker_id"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) ``` The inline `audio` storage and `take_blobs` still earn their place around the training process — listening back to an utterance in a notebook, sampling for human review, one-off evaluation against a held-out set, and the pre-extraction pass itself. Each of those reads a small, explicit set of blobs once. What the Train section above keeps off the per-batch hot path is exactly that raw-audio decode: paying it every step is what the pre-extracted features are designed to avoid. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") tbl = db.open_table("train_clean_100") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./librispeech-clean/data") local_tbl = local_db.open_table("train_clean_100") local_tbl.tags.create("minilm-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train_clean_100", version="minilm-v1") tbl_v5 = db.open_table("train_clean_100", version=5) ``` Pinning supports two workflows. A retrieval system locked to `minilm-v1` keeps returning stable results while the dataset evolves in parallel. A training experiment pinned to the same tag can be rerun later against the exact same utterances, so changes in metrics reflect model changes rather than data drift. ## 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 pipeline benefits from a local copy with fast random access to the FLAC bytes. 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 — including the `audio` column, which streams through Arrow record batches rather than being assembled in a single buffer. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/librispeech-clean-lance/data") remote_tbl = remote_db.open_table("train_clean_100") batches = ( remote_tbl.search() .where("speaker_id = 1272") .select(["id", "audio", "sampling_rate", "text", "speaker_id", "chapter_id", "text_emb"]) .to_batches() ) local_db = lancedb.connect("./librispeech-speaker-1272") local_db.create_table("train", batches) ``` The resulting `./librispeech-speaker-1272` 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/librispeech-clean-lance/data` for `./librispeech-speaker-1272`. ## Source & license Converted from [`openslr/librispeech_asr`](https://huggingface.co/datasets/openslr/librispeech_asr). LibriSpeech is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) and is built from the public-domain LibriVox audiobook corpus. ## Citation ``` @inproceedings{panayotov2015librispeech, title={LibriSpeech: An ASR corpus based on public domain audiobooks}, author={Panayotov, Vassil and Chen, Guoguo and Povey, Daniel and Khudanpur, Sanjeev}, booktitle={IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP)}, year={2015} } ``` # MNIST Source: https://docs.lancedb.com/datasets/mnist A Lance-formatted version of the classic MNIST handwritten-digit dataset covering 70,000 28×28 grayscale digits across ten balanced classes. Each row carries inline PNG bytes, the digit label, the human-readable class name, and a cosine-normalized… Source dataset card and downloadable files for `lance-format/mnist-lance`. A Lance-formatted version of the classic [MNIST handwritten-digit dataset](https://huggingface.co/datasets/ylecun/mnist) covering 70,000 28×28 grayscale digits across ten balanced classes. Each row carries inline PNG bytes, the digit label, the human-readable class name, and a cosine-normalized CLIP image embedding, all backed by a bundled `IVF_PQ` vector index plus scalar indices on the label columns and available directly from the Hub at `hf://datasets/lance-format/mnist-lance/data`. ## Key features * **Inline PNG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (OpenCLIP `ViT-B-32` / `laion2b_s34b_b79k`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index. * **Scalar indices on both label columns** — `BTREE` on `label` and `BITMAP` on `label_name` — so digit filters and digit-conditioned search are constant-time lookups. * **One columnar dataset** — scan labels cheaply, then fetch image bytes only for the rows you want. ## Splits | Split | Rows | | ------------- | ------ | | `train.lance` | 60,000 | | `test.lance` | 10,000 | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | -------------------------------------------------------- | | `id` | `int64` | Row index within the split (natural join key for merges) | | `image` | `large_binary` | Inline PNG bytes (28×28 grayscale) | | `label` | `int32` | Digit class id (0–9) | | `label_name` | `string` | Human-readable class (`"0"`..`"9"`) | | `image_emb` | `fixed_size_list` | CLIP image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BTREE` on `label` — fast equality and range filters on the digit id * `BITMAP` on `label_name` — fast filters across the ten class names ## 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 if your pipeline already speaks `Dataset` / `IterableDataset` or you want a quick streaming sample without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/mnist-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label"], row["label_name"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/mnist-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/mnist-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/mnist-lance --repo-type dataset --local-dir ./mnist-lance > ``` > > Then point Lance or LanceDB at `./mnist-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` turns nearest-neighbor lookup on the 512-d CLIP space into a single call. In production you would encode a query digit through OpenCLIP `ViT-B-32` at runtime and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding already stored in row 42 as a runnable stand-in so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/mnist-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "label"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label", "label_name"]) .limit(10) .to_list() ) print("query digit:", seed["label"]) for r in hits: print(f" id={r['id']:>5} label={r['label']}") ``` Because the embeddings are cosine-normalized and MNIST digits cluster tightly in CLIP space, near-neighbors of a seed image are dominated by the seed's own digit class — a useful sanity check before swapping in a real query encoder. Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency. ## Curate A typical curation pass for a digit-classification workflow narrows the table to a single digit (or a small set of confusable digits like 4/9 or 3/8) before sampling. Because both label columns are indexed, the filter resolves without scanning the embedding or image bytes; the bounded `.limit(500)` keeps the output small enough to inspect or hand off as a manifest of row ids. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/mnist-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where("label IN (4, 9)", prefilter=True) .select(["id", "label", "label_name"]) .limit(500) .to_list() ) print(f"{len(candidates)} 4/9 candidates") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `image_emb` columns are never read, so the network traffic for a 500-row candidate scan is dominated by the tiny label payload. ## 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_target_class` flag for binary one-vs-rest experiments and an `is_curvy_digit` flag that groups digits with curved strokes, 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 corpus first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./mnist-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "is_target_class": "label = 7", "is_curvy_digit": "label IN (0, 3, 6, 8, 9)", }) ``` If the values you want to attach already live in another table (offline labels from a stronger model, classifier predictions, per-row confidence scores), merge them in by joining on the `id` column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "pred_label": pa.array([5, 0, 4], type=pa.int32()), "pred_conf": pa.array([0.97, 0.88, 0.82]), }) tbl.merge(predictions, 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 second image encoder over the inline PNG bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. Columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/mnist-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the PNG bytes, normalize to [0, 1], forward, backward... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` to `select_columns(...)` on the next run skips PNG decoding entirely and reads only the cached 512-d vectors, which is the right shape for training a linear probe or a lightweight reranker on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/mnist-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./mnist-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 prediction columns or relabelings do not change what the tag resolves to. A training experiment pinned to the same tag can be rerun later against the exact same digits and labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/mnist-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("label IN (4, 9)") .select(["id", "image", "label", "label_name", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./mnist-4-vs-9") local_db.create_table("train", batches) ``` The resulting `./mnist-4-vs-9` 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/mnist-lance/data` for `./mnist-4-vs-9`. ## Source & license Converted from [`ylecun/mnist`](https://huggingface.co/datasets/ylecun/mnist). MNIST is released under the MIT license. The original dataset is by Yann LeCun, Corinna Cortes, and Christopher J.C. Burges. ## Citation ``` @article{lecun1998mnist, title={The MNIST database of handwritten digits}, author={LeCun, Yann and Cortes, Corinna and Burges, CJ}, url={http://yann.lecun.com/exdb/mnist/}, year={1998} } ``` # MS MARCO v2.1 Source: https://docs.lancedb.com/datasets/ms-marco-v2 A Lance-formatted version of MS MARCO v2.1 — Microsoft's machine-reading-comprehension benchmark built from anonymized Bing query logs. Each row is one user query, the up-to-10 candidate passages Bing retrieved for it with relevance flags, and the… Source dataset card and downloadable files for `lance-format/ms-marco-v2.1-lance`. A Lance-formatted version of [MS MARCO v2.1](https://huggingface.co/datasets/microsoft/ms_marco) — Microsoft's machine-reading-comprehension benchmark built from anonymized Bing query logs. Each row is one user query, the up-to-10 candidate passages Bing retrieved for it with relevance flags, and the human-written reference answers, with MiniLM query embeddings stored inline and pre-built ANN/FTS indices, available directly from the Hub at `hf://datasets/lance-format/ms-marco-v2.1-lance/data`. ## Key features * **Self-contained passage-ranking rows** — each query carries up to 10 candidate passages in parallel `passage_text` / `passage_url` / `passage_is_selected` columns, alongside the human-written `answers` and `well_formed_answers`. * **First relevant passage promoted to its own field** in `selected_passage`, so RAG / answer-evaluation workflows can read the gold context without indexing into the parallel passage lists. * **Pre-computed 384-dim query embeddings** (`query_emb`, `sentence-transformers/all-MiniLM-L6-v2`, cosine-normalized) with a bundled `IVF_PQ` index for semantic query lookup. * **One columnar dataset** — scan query metadata cheaply, defer the heavy passage text reads to the rows that matter. ## Splits | Split | Rows | | ------------------ | ------- | | `train.lance` | 808,731 | | `validation.lance` | 101,093 | ## Schema | Column | Type | Notes | | --------------------- | ------------------------------- | --------------------------------------------------------------- | | `query_id` | `int64` | MS MARCO query id | | `query` | `string` | The user's natural-language query | | `query_type` | `string` | One of `DESCRIPTION`, `NUMERIC`, `ENTITY`, `LOCATION`, `PERSON` | | `answers` | `list` | Human-written reference answers | | `well_formed_answers` | `list` | Reference answers re-written as full sentences | | `passage_text` | `list` | Up to 10 candidate passages | | `passage_url` | `list` | Source URLs for each candidate | | `passage_is_selected` | `list` | `1` if Bing labelled the passage relevant | | `selected_passage` | `string?` | First relevant passage (null if none) | | `query_emb` | `fixed_size_list` | MiniLM query embedding | ## Pre-built indices * `IVF_PQ` on `query_emb` — semantic query lookup (cosine) * `INVERTED` (FTS) on `query` and `selected_passage` — keyword and hybrid search * `BTREE` on `query_id` — stable lookup by identifier * `BITMAP` on `query_type` — cheap predicate evaluation for query class ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/ms-marco-v2.1-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["query"], "->", row["answers"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/docs)), and is the interface most users interact with. Each `.lance` file in `data/` is a table — open by name (`train`, `validation`). The same handle is used by the Search, Curate, Evolve, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/ms-marco-v2.1-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/ms-marco-v2.1-lance --repo-type dataset --local-dir ./ms-marco-v2.1-lance > ``` > > Then point Lance or LanceDB at `./ms-marco-v2.1-lance/data`. ## Search The bundled `IVF_PQ` index on `query_emb` makes nearest-neighbour query lookup a single call. In production you would encode an incoming user query through the same 384-dim MiniLM encoder used at ingest and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in so the snippet works without loading a model. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-lance/data") tbl = db.open_table("validation") seed = ( tbl.search() .select(["query_emb", "query"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["query_emb"], vector_column_name="query_emb") .metric("cosine") .where("query_type = 'NUMERIC'", prefilter=True) .select(["query_id", "query", "selected_passage", "answers"]) .limit(10) .to_list() ) for r in hits: print(r["query"], "->", (r["selected_passage"] or "")[:120]) ``` The result set carries only the projected columns; the 384-d `query_emb` is never read on the result side, and the full `passage_text` list is left untouched, keeping the working set small even when the underlying scan touches every row of the validation split. Because the dataset also ships an `INVERTED` index on both `query` and `selected_passage`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query against the gold passage. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase must literally appear in the relevant passage but the dense side still does most of the ranking. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["query_emb"]) .text("determinant matrix") .select(["query", "selected_passage", "answers"]) .limit(10) .to_list() ) for r in hybrid_hits: print(r["query"]) ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency for your workload. ## Curate A typical curation pass over MS MARCO starts by combining metadata filters with structural predicates over the parallel passage lists before any heavy text gets read. Lance evaluates the filter inside a single scan, so the candidate set comes back already filtered, and the bounded `.limit(1000)` keeps the output small enough to inspect. The example below assembles a set of numeric questions for which Bing labelled at least one passage relevant and the annotators produced a well-formed reference answer. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where( "query_type = 'NUMERIC' " "AND selected_passage IS NOT NULL " "AND array_length(well_formed_answers) > 0 " "AND length(query) >= 30", prefilter=True, ) .select(["query_id", "query", "answers", "well_formed_answers"]) .limit(1000) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['query']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `query_id`s, or hand to the Evolve and Train sections below. Neither `passage_text` nor `query_emb` is read by this scan, so a 1000-row curation pass against the Hub moves only kilobytes of metadata. ## 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 `query_length` column and a `num_selected` count over the parallel `passage_is_selected` list, 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 corpus. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./ms-marco-v2.1-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "query_length": "length(query)", "num_selected": "array_length(passage_is_selected)", "has_well_formed": "array_length(well_formed_answers) > 0", }) ``` If the values you want to attach already live in another table (cross-encoder reranker scores, generated-answer judgments, alternate embeddings from a stronger model), merge them in by joining on `query_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa reranker_scores = pa.table({ "query_id": pa.array([1185869, 9083, 524332], type=pa.int64()), "reranker_top1_score": pa.array([0.91, 0.47, 0.83]), }) tbl.merge(reranker_scores, on="query_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 encoder over the query text), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 reader-style QA model the natural projection is the query plus the gold passage and the answer; for a query-encoder retraining loop the precomputed embedding is enough on its own. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["query", "selected_passage", "answers"]) loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; tokenize, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["query_emb", "passage_text", "passage_is_selected"]` to `select_columns(...)` on the next run reads only those columns, which is the right shape for training a passage reranker on cached query 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./ms-marco-v2.1-lance/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("numeric-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="numeric-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A retrieval system locked to `numeric-v1` keeps returning stable passages while the dataset evolves in parallel — newly added reranker 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 queries and passages, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/ms-marco-v2.1-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where( "query_type = 'NUMERIC' " "AND selected_passage IS NOT NULL " "AND array_length(well_formed_answers) > 0" ) .select(["query_id", "query", "query_type", "answers", "well_formed_answers", "selected_passage", "query_emb"]) .to_batches() ) local_db = lancedb.connect("./ms-marco-numeric") local_db.create_table("train", batches) ``` The resulting `./ms-marco-numeric` 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/ms-marco-v2.1-lance/data` for `./ms-marco-numeric`. ## Source & license Converted from [`microsoft/ms_marco`](https://huggingface.co/datasets/microsoft/ms_marco) (`v2.1`). MS MARCO is released under the MIT license. ## Citation ``` @article{nguyen2016ms, title={MS MARCO: A Human Generated MAchine Reading COmprehension Dataset}, author={Nguyen, Tri and Rosenberg, Mir and Song, Xia and Gao, Jianfeng and Tiwary, Saurabh and Majumder, Rangan and Deng, Li}, journal={arXiv preprint arXiv:1611.09268}, year={2016} } ``` # Natural Questions Validation Source: https://docs.lancedb.com/datasets/natural-questions-val A Lance-formatted version of the Natural Questions validation split — 7,830 real Google search queries paired with the full Wikipedia article a human used to answer them, plus 1–5 annotator labels per question. MiniLM question embeddings are stored… Source dataset card and downloadable files for `lance-format/natural-questions-val-lance`. A Lance-formatted version of the [Natural Questions](https://ai.google.com/research/NaturalQuestions/) validation split — 7,830 real Google search queries paired with the full Wikipedia article a human used to answer them, plus 1–5 annotator labels per question. MiniLM question embeddings are stored inline and the dataset ships with pre-built ANN/FTS indices, all available directly from the Hub at `hf://datasets/lance-format/natural-questions-val-lance/data`. Sourced from [`google-research-datasets/natural_questions`](https://huggingface.co/datasets/google-research-datasets/natural_questions). > The NQ **train** split is 143 GB (307,373 rows); it is intentionally not bundled here. Add it via `natural_questions/dataprep.py --splits train` once disk and bandwidth allow. ## Key features * **Real Google search queries** with the full Wikipedia article that answers each one — `document_html` carries the inline UTF-8 HTML, so no sidecar files or external lookups are needed at query time. * **Annotator answer summaries** — `short_answers` aggregates and dedupes spans across all annotators, `yes_no_answer` carries the majority vote, and the `has_short_answer` / `has_long_answer` flags make annotation-coverage filters a single predicate. * **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 lookup. * **One columnar dataset** — scan question metadata cheaply, then read the heavy `document_html` only for the rows you actually want. ## Splits | Split | Rows | | ------------------ | ----- | | `validation.lance` | 7,830 | ## Schema | Column | Type | Notes | | ------------------- | ------------------------------- | ------------------------------------------------------- | | `id` | `string` | NQ example id | | `question` | `string` | Original Google search query | | `document_title` | `string` | Wikipedia article title | | `document_url` | `string` | Wikipedia article URL | | `document_html` | `large_binary` | Full HTML of the article (inline; UTF-8 bytes) | | `short_answers` | `list` | Deduped short-answer spans across all annotators | | `num_short_answers` | `int32` | Total annotator spans (incl. duplicates) | | `has_short_answer` | `bool` | At least one annotator provided a short-answer span | | `has_long_answer` | `bool` | At least one annotator selected a long-answer candidate | | `yes_no_answer` | `string` | `YES` / `NO` / `NONE` — majority vote across annotators | | `question_emb` | `fixed_size_list` | MiniLM question embedding | ## Pre-built indices * `IVF_PQ` on `question_emb` — semantic question lookup (cosine) * `INVERTED` (FTS) on `question` — keyword and hybrid search * `BTREE` on `id`, `document_title` — stable lookup by identifier * `BITMAP` on `yes_no_answer`, `has_short_answer`, `has_long_answer` — cheap predicate evaluation for annotation coverage ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/natural-questions-val-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["question"], "->", row["short_answers"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/natural-questions-val-lance/data/validation.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, ANN search, and HTML decoding are far faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/natural-questions-val-lance --repo-type dataset --local-dir ./natural-questions-val-lance > ``` > > Then point Lance or LanceDB at `./natural-questions-val-lance/data`. ## Search The bundled `IVF_PQ` index on `question_emb` makes nearest-neighbour question lookup a single call. In production you would encode an incoming user query through the same 384-dim MiniLM encoder used at ingest and pass the resulting vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in so the snippet works without loading a model. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") tbl = db.open_table("validation") 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("has_short_answer = TRUE", prefilter=True) .select(["question", "short_answers", "document_title"]) .limit(10) .to_list() ) for r in hits: print(r["question"], "->", r["short_answers"]) ``` The result set carries only the projected columns; the 384-d `question_emb` is never read on the result side, and the heavy `document_html` is left untouched, keeping the working set small even though each row carries a full Wikipedia article inline. Because the dataset also ships an `INVERTED` index on `question`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query against the question text. LanceDB merges the two result lists and reranks them in a single call, which is useful when a named entity must literally appear in the query but the dense side still does most of the ranking. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["question_emb"]) .text("declaration of independence") .select(["question", "short_answers", "document_title"]) .limit(10) .to_list() ) for r in hybrid_hits: print(r["question"]) ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency for your workload. ## Curate A typical curation pass over NQ starts with annotation-coverage filters before any HTML gets read. Lance evaluates the filter inside a single scan, so the candidate set comes back already filtered, and the bounded `.limit(500)` keeps the output small enough to inspect. The example below assembles a set of factoid questions with at least one short-answer span and a non-yes/no resolution. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where( "has_short_answer = TRUE " "AND yes_no_answer = 'NONE' " "AND array_length(short_answers) >= 1 " "AND length(question) >= 30", prefilter=True, ) .select(["id", "question", "short_answers", "document_title", "document_url"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['question']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of NQ example ids, or hand to the Evolve and Train sections below. The large `document_html` column is not read by this scan, so a 500-row curation pass against the Hub moves only kilobytes of metadata even though each row holds an entire Wikipedia article. ## 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` column, a `first_short_answer_length` derived from the deduped span list, and an `is_factoid` flag that combines the annotation flags, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./natural-questions-val-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "question_length": "length(question)", "first_short_answer_length": "length(short_answers[1])", "is_factoid": "has_short_answer = TRUE AND yes_no_answer = 'NONE'", }) ``` If the values you want to attach already live in another table (offline retriever scores, generated-answer judgments, alternate embeddings from a stronger model), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa retriever_scores = pa.table({ "id": pa.array(["797803103333068850", "5225754983651766092"]), "bm25_top1_score": pa.array([14.2, 8.7]), }) tbl.merge(retriever_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., extracting the long-answer paragraph from `document_html`), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 an open-domain QA reader the natural projection is the question plus the full document HTML and the answer spans; for a question-encoder retraining loop the precomputed embedding is enough on its own, and skipping `document_html` keeps each batch small. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") tbl = db.open_table("validation") train_ds = Permutation.identity(tbl).select_columns(["question", "document_html", "short_answers"]) loader = DataLoader(train_ds, batch_size=4, shuffle=True, num_workers=2) for batch in loader: # batch carries only the projected columns; tokenize, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["question_emb", "short_answers"]` to `select_columns(...)` on the next run reads only the 384-d vectors and the answer spans, which is the right shape for fine-tuning a retrieval head on cached embeddings without paying for the multi-megabyte `document_html` per row. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./natural-questions-val-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("factoid-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="factoid-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. A QA system locked to `factoid-v1` keeps returning stable answer spans while the dataset evolves in parallel — newly added retriever scores or labels do not change what the tag resolves to. An evaluation experiment pinned to the same tag can be rerun later against the exact same questions and articles, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/natural-questions-val-lance/data") remote_tbl = remote_db.open_table("validation") batches = ( remote_tbl.search() .where( "has_short_answer = TRUE " "AND yes_no_answer = 'NONE' " "AND array_length(short_answers) >= 1" ) .select(["id", "question", "document_title", "document_url", "short_answers", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./nq-factoid") local_db.create_table("validation", batches) ``` The resulting `./nq-factoid` 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/natural-questions-val-lance/data` for `./nq-factoid`. Note that this projection deliberately omits `document_html`; include it in the `.select(...)` list when the downstream task needs the article body. ## Source & license Converted from [`google-research-datasets/natural_questions`](https://huggingface.co/datasets/google-research-datasets/natural_questions). NQ is released under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) (matching the Wikipedia source). ## Citation ``` @article{kwiatkowski2019natural, title={Natural Questions: A Benchmark for Question Answering Research}, author={Kwiatkowski, Tom and Palomaki, Jennimaria and Redfield, Olivia and Collins, Michael and Parikh, Ankur and Alberti, Chris and Epstein, Danielle and Polosukhin, Illia and Devlin, Jacob and Lee, Kenton and Toutanova, Kristina and Jones, Llion and Kelcey, Matthew and Chang, Ming-Wei and Dai, Andrew M. and Uszkoreit, Jakob and Le, Quoc and Petrov, Slav}, journal={Transactions of the Association for Computational Linguistics}, year={2019} } ``` # OpenVid-1M Source: https://docs.lancedb.com/datasets/openvid A Lance-formatted version of the OpenVid-1M corpus — 937,957 high-quality clips with inline MP4 bytes, 1024-dim video embeddings, captions, and rich per-clip quality signals — available directly from the Hub at… Source dataset card and downloadable files for `lance-format/openvid-lance`. A Lance-formatted version of the [OpenVid-1M](https://huggingface.co/datasets/nkp37/OpenVid-1M) corpus — **937,957 high-quality clips** with inline MP4 bytes, 1024-dim video embeddings, captions, and rich per-clip quality signals — available directly from the Hub at `hf://datasets/lance-format/openvid-lance/data/train.lance`. ![](https://huggingface.co/datasets/nkp37/OpenVid-1M/resolve/main/OpenVid-1M.png) ## Key features * **Inline MP4 bytes** in the `video_blob` column, stored in a side blob file and surfaced as lazy `BlobFile` handles via `take_blobs` — metadata scans, search, and filtering never read a single byte of video data. * **Pre-computed 1024-dim video embeddings** in `embedding` with a bundled `IVF_PQ` ANN index. * **Pre-built `INVERTED` (FTS) index on `caption`** for keyword and hybrid search. * **Rich quality signals** — `aesthetic_score`, `motion_score`, `temporal_consistency_score`, `camera_motion`, `fps`, `seconds` — that downstream filters can stack on. ## Splits `train.lance` ## Schema | Column | Type | Notes | | ---------------------------- | -------------------------------- | ------------------------------------------------------------------------------------- | | `video_blob` | `large_binary` (blob-encoded) | Inline MP4 bytes; stored in a separate blob file and read lazily through `take_blobs` | | `video_path` | `string` | Original file path / object key | | `caption` | `string` | Text description of the clip | | `embedding` | `fixed_size_list` | Video embedding | | `aesthetic_score` | `float64` | Visual quality, roughly 0–6 | | `motion_score` | `float64` | Amount of motion, 0–1 | | `temporal_consistency_score` | `float64` | Frame-to-frame stability, 0–1 | | `camera_motion` | `string` | `pan`, `zoom`, `static`, etc. | | `fps` | `float64` | Frames per second | | `seconds` | `float64` | Clip duration | | `frame` | `int64` | Total frame count | ## Pre-built indices * `IVF_PQ` on `embedding` — video similarity (L2) * `INVERTED` (FTS) on `caption` — keyword and hybrid search ## 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 just want a quick streaming sample. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/openvid-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["caption"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/openvid-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 — or when you need the blob-level `take_blobs` entry point that streams video bytes lazily from inline storage. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/openvid-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, ANN search, and video decoding are far faster against a local copy: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/openvid-lance --repo-type dataset --local-dir ./openvid > ``` > > Then point Lance or LanceDB at `./openvid/data`. ## Search The bundled `IVF_PQ` index on `embedding` makes approximate-nearest-neighbor search a single call. In production you would encode a text prompt through a text-to-video model or a reference clip through the same video encoder used at ingest, and pass the resulting 1024-d vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/openvid-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["embedding", "caption"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["embedding"]) .metric("L2") .select(["caption", "aesthetic_score", "camera_motion", "seconds"]) .limit(10) .to_list() ) for r in hits: print(f"{r['aesthetic_score']:.2f} | {r['camera_motion']:>8} | {r['caption'][:60]}") ``` The result set carries only the projected columns. The `video_blob` column is never read, so the network traffic for a top-10 search is dominated by a few kilobytes of caption text, not by megabytes of MP4. The lazy blob fetch comes later — see Curate below. Because OpenVid also ships an `INVERTED` index on `caption`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["embedding"]) .text("sunset over the ocean") .select(["caption", "aesthetic_score", "seconds"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f"{r['aesthetic_score']:.2f} | {r['seconds']:.1f}s | {r['caption'][:60]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate Curation for a video workflow almost always starts as a metadata filter — pick the dynamic, high-aesthetic, well-stabilized clips first, then decide what to do with the video bytes. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(200)` makes it cheap to inspect or hand off. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/openvid-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where( "aesthetic_score >= 4.5 " "AND motion_score >= 0.3 " "AND temporal_consistency_score >= 0.9", prefilter=True, ) .select(["caption", "camera_motion", "aesthetic_score", "fps", "seconds"]) .limit(200) .with_row_id(True) .to_list() ) print(f"{len(candidates)} clips selected") ``` The scan above never reads the `video_blob` column. Lance stores blobs in a separate side file referenced by the dataset, so column-projected reads skip them entirely until they are explicitly requested. That is what makes "find me the right clips" a metadata-only operation against a million-row video corpus. Once the candidate set is fixed, pull the actual video bytes through pylance's `take_blobs`. It returns one `BlobFile` per row — a file-like handle that streams from inline blob storage on demand rather than reading the full clip into Python memory up front. For video specifically, this is the operation that matters: a video model trainer or a dataloader inspecting a few seconds of each clip should never have to materialize entire MP4s in memory just to inspect or decode part of them. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/openvid-lance/data/train.lance") row_ids = [r["_rowid"] for r in candidates[:10]] blob_files = ds.take_blobs("video_blob", ids=row_ids) ``` Each `BlobFile` implements the file protocol, so it can be passed straight to a decoder like PyAV without first being copied through a `bytes` object. The decoder seeks and reads against the underlying handle, which means a 2-second sample from a 30-second clip moves only the bytes the decoder actually touches — not the whole MP4. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import av with av.open(blob_files[0]) as container: stream = container.streams.video[0] for seconds in (0.0, 1.0, 2.5): target = int(seconds / stream.time_base) container.seek(target, stream=stream) frame = next( (f for f in container.decode(stream) if f.time is not None and f.time >= seconds), None, ) if frame is not None: print(f" seek {seconds:.1f}s -> {frame.width}x{frame.height} @ {frame.time:.2f}s") ``` If you only need the raw bytes (e.g., to persist a hand-picked subset to disk), call `.read()` on each handle. The lazy semantics are the same; `read()` simply materializes the full blob for that one row. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} for r_id, blob in zip(row_ids, blob_files): with open(f"clip_{r_id}.mp4", "wb") as f: f.write(blob.read()) ``` ## Evolve Lance stores each column independently, so a new column can be appended without rewriting the existing data — including the video blobs, which stay exactly where they are. 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 `duration_bucket` and a `is_high_quality` flag, either of which can then be used directly in `where` clauses without re-evaluating 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 first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./openvid/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "duration_bucket": ( "CASE WHEN seconds < 5 THEN 'short' " "WHEN seconds < 15 THEN 'medium' ELSE 'long' END" ), "is_high_quality": ( "aesthetic_score >= 4.5 AND temporal_consistency_score >= 0.9" ), }) ``` If the values you want to attach already live in another table (offline labels, safety classifications, a second embedding from a different encoder), merge them in by joining on `video_path`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa labels = pa.table({ "video_path": pa.array(["s3://openvid/clips/00001.mp4", "s3://openvid/clips/00002.mp4"]), "scene_label": pa.array(["beach", "city"]), }) tbl.merge(labels, on="video_path") ``` The original columns and the `video_blob` side file 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 an alternative video encoder over the inline bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## Train A common pattern for video training is to pre-extract decoded frames once into a derived LanceDB table, and train against that table with the regular projection-based dataloader. `take_blobs` is the mechanism that makes the extraction step tractable: each clip's MP4 is randomly addressable, so the pass can subset bytes on demand and write decoded windows into a fresh table without an external file store. Other workflows project `video_blob` directly through `select_columns(...)` and decode at the batch boundary, or skip pixels entirely and train on the cached embeddings — the right shape is workload-specific. The actual training loop is the same `Permutation.identity(tbl).select_columns(...)` snippet in every case; only the source table and the column list change. Against a pre-extracted frames table: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("./openvid-frames") # local table produced by the one-time extraction tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["frames", "caption", "aesthetic_score"]) loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=4) ``` Against the cached embeddings on the source table (no pre-extraction): ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader src_db = lancedb.connect("hf://datasets/lance-format/openvid-lance/data") src_tbl = src_db.open_table("train") train_ds = Permutation.identity(src_tbl).select_columns(["embedding", "caption"]) loader = DataLoader(train_ds, batch_size=256, shuffle=True, num_workers=4) ``` The inline `video_blob` storage and `take_blobs` still earn their place outside of the training loop — random-access inspection of a clip in a notebook, sampling for human review, one-off evaluation against a held-out set, and the pre-extraction step itself — but they are not the dataloader. ## 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, with the same blob handles still valid. You can list versions and inspect the history directly from the Hub copy; creating new tags requires a local copy since tags are writes. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/openvid-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./openvid/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("quality-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="quality-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A retrieval system locked to `quality-v1` keeps returning stable results while the dataset evolves in parallel — newly added columns 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 clips, 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 into the blob file. 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 — including the `video_blob` column, which streams through Arrow record batches rather than being assembled in a single buffer. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/openvid-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("aesthetic_score >= 4.5 AND motion_score >= 0.3") .select(["caption", "embedding", "video_blob", "aesthetic_score", "camera_motion"]) .to_batches() ) local_db = lancedb.connect("./openvid-subset") local_db.create_table("train", batches) ``` The resulting `./openvid-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/openvid-lance/data` for `./openvid-subset`. The same `take_blobs` pattern from Curate also works against the local copy — and runs faster, because the blob side file is now on local disk. ## Citation ``` @article{nan2024openvid, title={OpenVid-1M: A Large-Scale High-Quality Dataset for Text-to-video Generation}, author={Nan, Kepan and Xie, Rui and Zhou, Penghao and Fan, Tiehan and Yang, Zhenheng and Chen, Zhijie and Li, Xiang and Yang, Jian and Tai, Ying}, journal={arXiv preprint arXiv:2407.02371}, year={2024} } ``` ## License Content inherits the original OpenVid-1M dataset license. Review the [upstream dataset card](https://huggingface.co/datasets/nkp37/OpenVid-1M) before downstream use. # Oxford-IIIT Pet Source: https://docs.lancedb.com/datasets/oxford-pets A Lance-formatted version of the Oxford-IIIT Pet dataset — 7,390 cat and dog photos across 37 breeds — sourced from pcuenq/oxford-pets. Each row carries the inline JPEG bytes, the breed name, a species flag distinguishing cats from dogs, and a… Source dataset card and downloadable files for `lance-format/oxford-pets-lance`. A Lance-formatted version of the [Oxford-IIIT Pet](https://www.robots.ox.ac.uk/~vgg/data/pets/) dataset — 7,390 cat and dog photos across 37 breeds — sourced from [`pcuenq/oxford-pets`](https://huggingface.co/datasets/pcuenq/oxford-pets). Each row carries the inline JPEG bytes, the breed name, a species flag distinguishing cats from dogs, and a cosine-normalized CLIP image embedding, all available directly from the Hub at `hf://datasets/lance-format/oxford-pets-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (`image_emb`, OpenCLIP `ViT-B-32`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for similarity search. * **Both breed and species labels** (`label_name`, `is_dog`) so a query can target a specific breed, all dogs, or all cats by stacking simple predicates. * **Bitmap indices on both label columns** make species- and breed-based curation a cheap predicate rather than a full scan. ## Splits | Split | Rows | Notes | | ------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------- | | `train.lance` | 7,390 | The `pcuenq/oxford-pets` source mirror ships a single split; the canonical Oxford-IIIT trainval/test partition is not pre-applied here. | ## Schema | Column | Type | Notes | | ------------ | ------------------------------- | -------------------------------------------------------------------------------- | | `id` | `int64` | Row index within split (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes (quality 92) | | `label_name` | `string` | One of 37 breeds, underscore-spaced (`british_shorthair`, `golden_retriever`, …) | | `is_dog` | `bool` | `true` for dog breeds, `false` for cat breeds | | `path` | `string?` | Original filename from the source dataset | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `BITMAP` on `label_name` — fast lookup by breed * `BITMAP` on `is_dog` — fast species filter ## 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/oxford-pets-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label_name"], row["is_dog"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/oxford-pets-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/oxford-pets-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/oxford-pets-lance --repo-type dataset --local-dir ./oxford-pets-lance > ``` > > Then point Lance or LanceDB at `./oxford-pets-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes approximate-nearest-neighbor search a single call. In production you would encode a query photo through the same OpenCLIP `ViT-B-32` model used at ingest and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding stored in row 0 as a runnable stand-in so the snippet works without a model loaded; on a clean run the first hit is expected to be the seed image itself, which is a useful sanity check on the index. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/oxford-pets-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "label_name", "is_dog"]) .limit(1) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label_name", "is_dog"]) .limit(10) .to_list() ) print(f"seed: {seed['label_name']} (is_dog={seed['is_dog']})") for r in hits: print(f" {r['id']:>5} {r['label_name']:<22} is_dog={r['is_dog']}") ``` Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency for your workload. ## Curate A typical curation pass for a fine-grained pet classifier stacks the species predicate and a breed predicate inside a single filtered scan. With bitmap indices on both `label_name` and `is_dog`, the result comes back in milliseconds, and the bounded `.limit(200)` keeps it small enough to inspect or hand off to a training run. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/oxford-pets-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where("is_dog = true AND label_name IN ('golden_retriever', 'beagle', 'pug')") .select(["id", "label_name", "is_dog", "path"]) .limit(200) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['label_name']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `image_emb` columns are never read by this query, so the network traffic is dominated by the small label fields rather than JPEG bytes or vectors. ## 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 derives a `species` string from the `is_dog` boolean and adds a coarse breed-group flag for terriers, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./oxford-pets-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "species": "CASE WHEN is_dog THEN 'dog' ELSE 'cat' END", "is_terrier": "label_name LIKE '%terrier%'", }) ``` If the values you want to attach already live in another table (offline labels, classifier predictions, an integer class id), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa class_ids = pa.table({ "id": pa.array([0, 1, 2]), "label_int": pa.array([0, 0, 17]), }) tbl.merge(class_ids, 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 second embedding model over the JPEG bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. For a from-scratch breed classifier, project the JPEG bytes and the string breed label; for a linear probe on top of frozen CLIP features, swap the projection to the embedding column and skip JPEG decoding entirely. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/oxford-pets-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label_name"]) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the JPEG bytes, map label_name -> int via a class list, forward, cross-entropy... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label_name"]` to `select_columns(...)` on the next run reads only the cached 512-d vectors and the label, which is the right shape for a linear probe or a lightweight reranker. Projecting `["image", "is_dog"]` reduces the task to binary species classification on the same data. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/oxford-pets-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./oxford-pets-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 columns 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 images and labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/oxford-pets-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("is_dog = true") .select(["id", "image", "label_name", "is_dog", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./oxford-pets-dogs-subset") local_db.create_table("train", batches) ``` The resulting `./oxford-pets-dogs-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/oxford-pets-lance/data` for `./oxford-pets-dogs-subset`. ## Source & license Converted from [`pcuenq/oxford-pets`](https://huggingface.co/datasets/pcuenq/oxford-pets). Released under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). ## Citation ``` @inproceedings{parkhi2012cats, title={Cats and Dogs}, author={Parkhi, Omkar M. and Vedaldi, Andrea and Zisserman, Andrew and Jawahar, C. V.}, booktitle={IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2012} } ``` # Pascal VOC 2012 Segmentation Source: https://docs.lancedb.com/datasets/pascal-voc-2012-segmentation A Lance-formatted version of the Pascal VOC 2012 semantic segmentation split, sourced from nateraw/pascal-voc-2012. Each row pairs an inline JPEG image with the per-pixel PNG segmentation mask and a cosine-normalized OpenCLIP ViT-B-32 image… Source dataset card and downloadable files for `lance-format/pascal-voc-2012-segmentation-lance`. A Lance-formatted version of the [Pascal VOC 2012 semantic segmentation split](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/), sourced from [`nateraw/pascal-voc-2012`](https://huggingface.co/datasets/nateraw/pascal-voc-2012). Each row pairs an inline JPEG image with the per-pixel PNG segmentation mask and a cosine-normalized OpenCLIP `ViT-B-32` image embedding, so a single columnar table carries both annotation modalities and the features needed to retrieve, curate, and train against them — all available directly from the Hub at `hf://datasets/lance-format/pascal-voc-2012-segmentation-lance/data`. ## Key features * **Inline JPEG bytes and inline PNG mask bytes in the same row** — image and per-pixel segmentation travel together with no sidecar folders or mask lookups. * **Pre-computed CLIP image embeddings** (`image_emb`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for visual similarity search. * **Standard VOC class encoding** — mask pixel values are class ids in `0..20` plus `255` for void, identical to the official VOC palette. * **One columnar dataset** — scan image-level metadata cheaply, then fetch image or mask bytes only for the rows you actually want. The 20 Pascal VOC foreground classes are: `aeroplane`, `bicycle`, `bird`, `boat`, `bottle`, `bus`, `car`, `cat`, `chair`, `cow`, `diningtable`, `dog`, `horse`, `motorbike`, `person`, `pottedplant`, `sheep`, `sofa`, `train`, `tvmonitor`. ## Splits | Split | Rows | Notes | | ------------------ | ----- | ------------------------------------ | | `train.lance` | 1,464 | Official VOC 2012 segmentation train | | `validation.lance` | 1,449 | Official VOC 2012 segmentation val | ## Schema | Column | Type | Notes | | ----------- | ------------------------------- | -------------------------------------------------------------------------------- | | `id` | `int64` | Row index within the split (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes | | `mask` | `large_binary` | Inline PNG bytes — class id per pixel (0=background, 1-20=VOC classes, 255=void) | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — `metric=cosine` > Note: the small split sizes (≤1,464 rows) sit below Lance's default partition count, so the helper falls back to a smaller `num_partitions` automatically. For higher recall, rebuild the index with `num_partitions=16` against a local copy. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/pascal-voc-2012-segmentation-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["id"], len(row["image"]), len(row["mask"])) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/pascal-voc-2012-segmentation-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/pascal-voc-2012-segmentation-lance --repo-type dataset --local-dir ./pascal-voc-2012-segmentation-lance > ``` > > Then point Lance or LanceDB at `./pascal-voc-2012-segmentation-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes visual nearest-neighbour search a single call. In production you would encode a query image (or a class prototype) through OpenCLIP `ViT-B-32` at runtime and pass the resulting 512-d cosine-normalized vector to `tbl.search(...)`. The example below uses the embedding from row 42 as a runnable stand-in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb"]) .limit(1) .offset(42) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id"]) .limit(10) .to_list() ) for r in hits: print(r["id"]) ``` Because the embeddings are cosine-normalized, `metric="cosine"` is the natural choice and the first hit is typically the seed row itself — a useful sanity check before tuning `nprobes` and `refine_factor` for recall. ## Curate A typical curation pass for a segmentation workflow combines visual similarity with a structural filter on the row. Stacking both inside a single filtered scan keeps the candidate set small and explicit, and the bounded `.limit(200)` makes it cheap to inspect before committing to anything downstream. The snippet below seeds from row 42 and restricts the candidates to rows whose mask payload is non-trivially sized — a cheap proxy for masks that actually carry foreground annotation. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb"]) .limit(1) .offset(42) .to_list()[0] ) candidates = ( tbl.search(seed["image_emb"]) .metric("cosine") .where("octet_length(mask) > 2000", prefilter=True) .select(["id"]) .limit(200) .to_list() ) print(f"{len(candidates)} candidates") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` and `mask` columns are never read in the candidate scan, so the network traffic stays dominated by the embedding vectors rather than image or mask 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 `image_bytes` size and a `has_mask` flag, both 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./pascal-voc-2012-segmentation-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "image_bytes": "octet_length(image)", "has_mask": "octet_length(mask) > 1024", }) ``` For class-level statistics — for example, a per-row list of class ids present in the mask, or a per-class pixel count — the values cannot be derived in SQL because they require decoding the PNG. Compute them once in an external table and join in by `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa class_stats = pa.table({ "id": pa.array([0, 1, 2], type=pa.int64()), "classes_present": pa.array([[15], [7, 15], [9]], type=pa.list_(pa.int8())), }) tbl.merge(class_stats, 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 running a model over the image bytes (a second-pass embedding, an instance segmentation, a depth prediction), Lance also provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 segmentation run, project the JPEG bytes and the PNG mask bytes together; everything else, including the CLIP embeddings, stays on disk until you opt in. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "mask"]) loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb stays on disk. # decode the JPEGs and PNGs, build (image, label) tensors, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["image_emb"]` to `select_columns(...)` on the next run skips JPEG and PNG decoding entirely and reads only the cached 512-d vectors, which is the right shape for training a lightweight linear probe over frozen CLIP features. ## Versioning Every mutation to a Lance dataset, whether it adds a column, merges class statistics, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./pascal-voc-2012-segmentation-lance/data") local_tbl = local_db.open_table("train") local_tbl.tags.create("voc2012-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="voc2012-clip-vitb32-v1") tbl_v5 = db.open_table("train", version=5) ``` Pinning supports two workflows. A retrieval system locked to `voc2012-clip-vitb32-v1` keeps returning stable results while the dataset evolves in parallel — newly added class statistics or alternative embeddings do not change what the tag resolves to. A training experiment pinned to the same tag can be rerun later against the exact same image/mask pairs, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/pascal-voc-2012-segmentation-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("octet_length(mask) > 2000") .select(["id", "image", "mask", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./voc-subset") local_db.create_table("train", batches) ``` The resulting `./voc-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/pascal-voc-2012-segmentation-lance/data` for `./voc-subset`. ## Source & license Converted from [`nateraw/pascal-voc-2012`](https://huggingface.co/datasets/nateraw/pascal-voc-2012). The Pascal VOC dataset is released under [its own custom license](http://host.robots.ox.ac.uk/pascal/VOC/) — please review before redistribution. ## Citation ``` @misc{everingham2012pascal, title={The Pascal Visual Object Classes Challenge: A Retrospective}, author={Everingham, Mark and Eslami, S. M. Ali and Van Gool, Luc and Williams, Christopher K. I. and Winn, John and Zisserman, Andrew}, journal={International Journal of Computer Vision}, year={2015} } ``` # SQuAD v2 Source: https://docs.lancedb.com/datasets/squad-v2 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… Source dataset card and downloadable files for `lance-format/squad-v2-lance`. A Lance-formatted version of [SQuAD v2](https://huggingface.co/datasets/rajpurkar/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 | Split | Rows | | ------------------ | ------- | | `train.lance` | 130,319 | | `validation.lance` | 11,873 | ## Schema | Column | Type | Notes | | --------------- | ------------------------------- | ------------------------------------------------------ | | `id` | `string` | SQuAD question id (natural join key for merges) | | `title` | `string` | Wikipedia article title | | `context` | `string` | Paragraph the question was generated from | | `question` | `string` | The question text | | `answers` | `list` | Accepted answer spans (empty for impossible questions) | | `answer_starts` | `list` | Character offsets of each answer within `context` | | `is_impossible` | `bool` | `true` for SQuAD 2.0 unanswerable questions | | `question_emb` | `fixed_size_list` | MiniLM embedding of `question` (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `question_emb` — `metric=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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > 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`. ## Search 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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](https://lance.org/guide/data_evolution/). ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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`](https://huggingface.co/datasets/rajpurkar/squad_v2). SQuAD v2 is distributed under [CC BY-SA 4.0](https://creativecommons.org/licenses/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} } ``` # Stanford Cars Source: https://docs.lancedb.com/datasets/stanford-cars A Lance-formatted version of the Stanford Cars fine-grained benchmark — 8,144 photographs across 196 make/model/year classes — sourced from Multimodal-Fatima/StanfordCars_train. Each row carries the inline JPEG bytes, the integer class id, a… Source dataset card and downloadable files for `lance-format/stanford-cars-lance`. A Lance-formatted version of the [Stanford Cars](https://web.archive.org/web/20210212183835/http://ai.stanford.edu/~jkrause/cars/car_dataset.html) fine-grained benchmark — 8,144 photographs across 196 make/model/year classes — sourced from [`Multimodal-Fatima/StanfordCars_train`](https://huggingface.co/datasets/Multimodal-Fatima/StanfordCars_train). Each row carries the inline JPEG bytes, the integer class id, a BLIP-generated caption inherited from the source mirror, and a cosine-normalized CLIP image embedding, all available directly from the Hub at `hf://datasets/lance-format/stanford-cars-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files, no image folders. * **Pre-computed CLIP image embeddings** (`image_emb`, OpenCLIP `ViT-B-32`, 512-dim, cosine-normalized) with a bundled `IVF_PQ` index for similarity search. * **BLIP captions in `blip_caption`** with a full-text index, so keyword search on visual descriptions composes with vector search in a single query. * **A bundled scalar index on `label`** makes class-based curation a cheap predicate rather than a full scan. ## Splits | Split | Rows | Notes | | ------------- | ----- | ----------------------------------------------------------------------------------------------------------- | | `train.lance` | 8,144 | The source mirror redistributes a single split; the original Stanford Cars test split is not included here. | ## Schema | Column | Type | Notes | | -------------- | ------------------------------- | ---------------------------------------------------------------------- | | `id` | `int64` | Row index within split (natural join key for merges) | | `image` | `large_binary` | Inline JPEG bytes (quality 92) | | `label` | `int32` | Class id (0–195), one per Make Model Year combination | | `blip_caption` | `string?` | BLIP-generated caption (beam=5) carried through from the source mirror | | `image_emb` | `fixed_size_list` | OpenCLIP `ViT-B-32` image embedding (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `image_emb` — vector similarity search (cosine) * `INVERTED` (FTS) on `blip_caption` — keyword and hybrid search * `BTREE` on `label` — fast lookup by class 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 without installing anything Lance-specific. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/stanford-cars-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["label"], row["blip_caption"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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, Train, Versioning, and Materialize-a-subset sections below. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/stanford-cars-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 or operate on dataset internals — schema, scanner, fragments, and the list of pre-built indices. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/stanford-cars-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/stanford-cars-lance --repo-type dataset --local-dir ./stanford-cars-lance > ``` > > Then point Lance or LanceDB at `./stanford-cars-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes approximate-nearest-neighbor search a single call. In production you would encode a query photo through the same OpenCLIP `ViT-B-32` model used at ingest and pass the resulting 512-d vector to `tbl.search(...)`. The example below uses the embedding stored in row 0 as a runnable stand-in so the snippet works without any model loaded. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/stanford-cars-lance/data") tbl = db.open_table("train") seed = ( tbl.search() .select(["image_emb", "blip_caption"]) .limit(1) .to_list()[0] ) hits = ( tbl.search(seed["image_emb"]) .metric("cosine") .select(["id", "label", "blip_caption"]) .limit(10) .to_list() ) print("seed caption:", seed["blip_caption"]) for r in hits: print(f" {r['id']:>6} label={r['label']:>3} {r['blip_caption'][:60]}") ``` Tune `metric`, `nprobes`, and `refine_factor` to trade recall against latency for your workload. Because the dataset also ships an `INVERTED` index on `blip_caption`, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges the two result lists and reranks them in a single call, which is useful when a phrase like "red convertible" must literally appear in the caption but you still want CLIP to do the heavy lifting on visual similarity. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["image_emb"]) .text("red convertible") .select(["id", "label", "blip_caption"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['id']:>6} label={r['label']:>3} {r['blip_caption'][:60]}") ``` ## Curate A typical curation pass for a fine-grained classifier combines a class-based filter with a content filter on the caption. Stacking both inside a single filtered scan keeps the result small and explicit, and the bounded `.limit(200)` makes it cheap to inspect before committing the subset to anything downstream. The `BTREE` on `label` and the `INVERTED` index on `blip_caption` make both predicates effectively free. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/stanford-cars-lance/data") tbl = db.open_table("train") candidates = ( tbl.search("convertible OR coupe") .where("label IN (12, 47, 89)", prefilter=True) .select(["id", "label", "blip_caption"]) .limit(200) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['blip_caption'][:80]}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `id`s, or feed into the Evolve and Train workflows below. The `image` column is never read, so the network traffic for a 200-row candidate scan is dominated by the small caption 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. Stanford Cars class strings often encode the model year as a trailing four-digit token in the caption; the example below uses a SQL regex to lift that year into its own column, and adds a flag for vintage cars. Either 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./stanford-cars-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "caption_year": "CAST(regexp_extract(blip_caption, '(\\d{4})', 1) AS INTEGER)", "is_long_caption": "length(blip_caption) >= 80", }) ``` If the values you want to attach already live in another table (offline labels, classifier predictions, the Make Model Year strings from the original Stanford metadata), merge them in by joining on `id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa class_strings = pa.table({ "id": pa.array([0, 1, 2]), "class_name": pa.array([ "AM General Hummer SUV 2000", "Acura RL Sedan 2012", "Acura TL Sedan 2012", ]), }) tbl.merge(class_strings, 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 second captioner over the image bytes), Lance provides a batch-UDF API in the underlying library — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/) for that pattern. ## 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 prefetch, shuffling, and batching behave as in any PyTorch pipeline. For a from-scratch fine-grained classifier, project the JPEG bytes and the integer label; for a linear probe on top of frozen CLIP features, swap the projection to the embedding column and skip JPEG decoding entirely. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/stanford-cars-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "label"]) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; image_emb and blip_caption stay on disk. # decode the JPEG bytes, forward, cross-entropy against `label`... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "label"]` to `select_columns(...)` on the next run reads only the cached 512-d vectors and the label, which is the right shape for a linear probe or a lightweight reranker. ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/stanford-cars-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./stanford-cars-lance/data") local_tbl = local_db.open_table("train") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("train", version="clip-vitb32-v1") tbl_v5 = db.open_table("train", 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 columns or captions 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 labels, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/stanford-cars-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search("convertible OR coupe") .select(["id", "image", "label", "blip_caption", "image_emb"]) .to_batches() ) local_db = lancedb.connect("./stanford-cars-sports-subset") local_db.create_table("train", batches) ``` The resulting `./stanford-cars-sports-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/stanford-cars-lance/data` for `./stanford-cars-sports-subset`. ## Source & license Converted from [`Multimodal-Fatima/StanfordCars_train`](https://huggingface.co/datasets/Multimodal-Fatima/StanfordCars_train), itself a redistribution of the Stanford Cars dataset. The original dataset license is for non-commercial research use; review the [Stanford Cars terms](https://github.com/jhoffman/stanford-cars) before redistribution. ## Citation ``` @inproceedings{krause2013collecting, title={Collecting a large-scale dataset of fine-grained cars}, author={Krause, Jonathan and Stark, Michael and Deng, Jia and Fei-Fei, Li}, booktitle={Workshop on Fine-Grained Visual Categorization (CVPR)}, year={2013} } ``` # TextVQA Source: https://docs.lancedb.com/datasets/textvqa A Lance-formatted version of TextVQA — visual question answering where the question requires reading text in the image (street signs, product labels, screen captures) — sourced from lmms-lab/textvqa. Each row carries the image bytes, the question… Source dataset card and downloadable files for `lance-format/textvqa-lance`. A Lance-formatted version of [TextVQA](https://textvqa.org/) — visual question answering where the question requires *reading* text in the image (street signs, product labels, screen captures) — sourced from [`lmms-lab/textvqa`](https://huggingface.co/datasets/lmms-lab/textvqa). Each row carries the image bytes, the question, the 10 reference annotator answers, the OCR tokens detected by the source pre-processing, OpenImages-style scene tags, and paired CLIP image and question embeddings — all available directly from the Hub at `hf://datasets/lance-format/textvqa-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files or image folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `question_emb` (OpenCLIP ViT-B/32, 512-dim, cosine-normalized) — so cross-modal text→image retrieval is one indexed lookup. * **OCR tokens travel with the row** in `ocr_tokens`, which makes OCR-aware filtering and reranking a single SQL predicate alongside the visual and textual features. * **Pre-built ANN, FTS, and scalar indices** covering both embeddings, the question and canonical answer, and the source partition. ## Splits | Split | Rows | | ------------------ | ------ | | `validation.lance` | 5,000 | | `train.lance` | 34,602 | ## Schema | Column | Type | Notes | | --------------- | ------------------------------- | ------------------------------------------------------- | | `id` | `int64` | Row index within split | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `string?` | TextVQA image id | | `question_id` | `string?` | TextVQA question id | | `question` | `string` | The question text | | `answers` | `list` | 10 annotator answers | | `answer` | `string` | First annotator answer (used as canonical / FTS target) | | `ocr_tokens` | `list` | OCR tokens detected on the image | | `image_classes` | `list` | OpenImages-style scene tags from the source | | `set_name` | `string?` | Source partition (`train`, `val`) | | `image_emb` | `fixed_size_list` | OpenCLIP image embedding (cosine-normalized) | | `question_emb` | `fixed_size_list` | OpenCLIP 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 * `BTREE` on `image_id`, `question_id`, `set_name` — fast lookup by id and partition ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/textvqa-lance", split="validation", 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](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/textvqa-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/textvqa-lance --repo-type dataset --local-dir ./textvqa-lance > ``` > > Then point Lance or LanceDB at `./textvqa-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes cross-modal text→image retrieval a single call: encode a question with the same OpenCLIP model used at ingest (ViT-B/32 `laion2b_s34b_b79k`, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") tbl = db.open_table("validation") 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", "ocr_tokens"]) .limit(10) .to_list() ) print("query question:", seed["question"], "->", seed["answer"]) for r in hits: print(f" {r['image_id']:>14} {r['question'][:60]} ocr={r['ocr_tokens'][:5]}") ``` Because the CLIP embeddings are cosine-normalized, cosine is the right metric and the first hit will often be the source row itself. Swap `vector_column_name="image_emb"` for `question_emb` to find paraphrased questions instead of visually similar images. Because the dataset also ships an `INVERTED` index on `question` and `answer`, the same query can be issued as a hybrid search that combines the dense vector with a literal keyword match. This is particularly useful for TextVQA, where a brand name or sign content like "stop" must literally appear in the question (or eventually, the answer) while CLIP handles the visual side. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="image_emb") .vector(seed["question_emb"]) .text("brand name") .select(["image_id", "question", "answer", "ocr_tokens"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['image_id']:>14} {r['question'][:60]} -> {r['answer']}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate Curation passes for TextVQA usually combine an OCR-driven structural filter (does this image actually contain a meaningful amount of detected text?) with a content predicate on the question or the canonical answer, so the candidate set is both visually interesting and topically focused. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where( "array_length(ocr_tokens) >= 5 AND question LIKE '%brand%' AND length(answer) > 0", prefilter=True, ) .select(["question_id", "image_id", "question", "answer", "ocr_tokens"]) .limit(500) .to_list() ) print(f"{len(candidates)} candidates; first: {candidates[0]['question']} -> {candidates[0]['answer']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `question_id`s, 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 question, answer, and OCR-token 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 `ocr_token_count`, an `is_yes_no_question` flag, and an `answer_length` integer, 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 split first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./textvqa-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "ocr_token_count": "array_length(ocr_tokens)", "is_yes_no_question": "lower(answer) IN ('yes', 'no')", "answer_length": "length(answer)", "question_length": "length(question)", }) ``` If the values you want to attach already live in another table (a stronger OCR system's tokens, a model's predicted answer, an annotator-disagreement score), merge them in by joining on `question_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "question_id": pa.array(["34602", "34603"]), "model_answer": pa.array(["pepsi", "exit"]), "model_confidence": pa.array([0.88, 0.72]), }) 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 (e.g., running an alternate OCR engine over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 TextVQA fine-tune that needs OCR conditioning, project the JPEG bytes, the question, the OCR tokens, and the canonical answer; columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns(["image", "question", "ocr_tokens", "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 and OCR tokens, forward through the VLM, # compute the loss against `answer`... ... ``` 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 training a lightweight reranker or a linear probe on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./textvqa-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("openclip-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="openclip-vitb32-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. A retrieval system locked to `openclip-vitb32-v1` keeps returning stable results while the dataset evolves in parallel — newly added OCR systems or model predictions 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, questions, and OCR tokens, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/textvqa-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("array_length(ocr_tokens) >= 5") .select(["question_id", "image_id", "image", "question", "answer", "ocr_tokens", "image_emb", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./textvqa-ocr-rich") local_db.create_table("train", batches) ``` The resulting `./textvqa-ocr-rich` 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/textvqa-lance/data` for `./textvqa-ocr-rich`. ## Source & license Converted from [`lmms-lab/textvqa`](https://huggingface.co/datasets/lmms-lab/textvqa). TextVQA is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by Singh et al. (Facebook AI Research). ## Citation ``` @inproceedings{singh2019towards, title={Towards VQA models that can read}, author={Singh, Amanpreet and Natarajan, Vivek and Shah, Meet and Jiang, Yu and Chen, Xinlei and Batra, Dhruv and Parikh, Devi and Rohrbach, Marcus}, booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2019} } ``` # TriviaQA Source: https://docs.lancedb.com/datasets/trivia-qa A Lance-formatted version of TriviaQA (rc.nocontext config) — a large reading-comprehension dataset of trivia questions paired with a canonical answer, accepted aliases, and entity-type metadata — with MiniLM question embeddings stored inline and… Source dataset card and downloadable files for `lance-format/trivia-qa-lance`. A Lance-formatted version of [TriviaQA](https://nlp.cs.washington.edu/triviaqa/) (`rc.nocontext` config) — a large reading-comprehension dataset of trivia questions paired with a canonical answer, accepted aliases, and entity-type metadata — with MiniLM question embeddings stored inline and ready for retrieval at `hf://datasets/lance-format/trivia-qa-lance/data`. The `rc.nocontext` slice is the standard reading-comprehension form without the multi-gigabyte `entity_pages` / `search_results` payloads, which keeps the dataset compact while preserving everything needed for closed-book QA, retrieval research, and as a search target. ## Key features * **138k+ trivia questions** with a canonical `answer_value`, normalized form for exact-match scoring, and a list of accepted `answer_aliases`. * **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 index** on `question` for keyword search and hybrid retrieval. * **One columnar dataset** carrying questions, canonical answers, aliases, types, and embeddings together — project only the columns each query needs. ## Splits | Split | Rows | | ------------------ | ------- | | `train.lance` | 138,384 | | `validation.lance` | 17,944 | ## Schema | Column | Type | Notes | | ------------------- | ------------------------------- | ---------------------------------------------------------------------------- | | `question_id` | `string` | TriviaQA question id (e.g. `tc_1`); natural join key for merges | | `question` | `string` | The trivia question | | `question_source` | `string` | URL or source the question came from | | `answer_value` | `string` | Canonical answer | | `answer_aliases` | `list` | Other accepted phrasings (e.g. `["Sinclair Lewis", "Harry Sinclair Lewis"]`) | | `normalized_answer` | `string` | Lowercased / normalized form for exact-match scoring | | `answer_type` | `string` | TriviaQA entity type (e.g. `WikipediaEntity`, `FreebaseEntity`) | | `question_emb` | `fixed_size_list` | MiniLM embedding of `question` (cosine-normalized) | ## Pre-built indices * `IVF_PQ` on `question_emb` — `metric=cosine`, vector similarity search * `INVERTED` on `question` — full-text search * `BTREE` on `question_id` and `answer_value` — point lookups and prefix scans * `BITMAP` on `answer_type` — fast filtering by entity type ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/trivia-qa-lance", split="train", streaming=True) for row in hf_ds.take(3): print(row["question"], "->", row["answer_value"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/trivia-qa-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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/trivia-qa-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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/trivia-qa-lance --repo-type dataset --local-dir ./trivia-qa-lance > ``` > > Then point Lance or LanceDB at `./trivia-qa-lance/data`. ## Search The bundled `IVF_PQ` index on `question_emb` turns semantic retrieval over trivia questions 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/trivia-qa-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") .select(["question_id", "question", "answer_value", "answer_aliases"]) .limit(10) .to_list() ) for r in hits: print(f"{r['answer_value']:30s} | {r['question'][:80]}") ``` Because the recommended setup also builds an `INVERTED` index on `question`, 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 specific named entity must literally appear in the question but the dense side should still drive ranking. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid") .vector(seed["question_emb"]) .text("sistine chapel") .select(["question_id", "question", "answer_value", "answer_aliases"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f"{r['answer_value']:30s} | {r['question'][:80]}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate TriviaQA's `answer_type` column — backed by a `BITMAP` index — makes it cheap to slice the dataset by entity category, and the question text itself is a useful predicate for filtering out very short or unusually long items. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/trivia-qa-lance/data") tbl = db.open_table("train") candidates = ( tbl.search() .where( "answer_type = 'WikipediaEntity' " "AND length(question) BETWEEN 60 AND 300", prefilter=True, ) .select(["question_id", "question", "answer_value", "answer_aliases"]) .limit(1000) .to_list() ) print(f"{len(candidates)} candidates; first answer: {candidates[0]['answer_value']}") ``` Neither the `question_emb` vector nor the unused alias fields drive this scan, so a 1000-row curation pass against the Hub moves only the projected text columns. 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_aliases` count, and a `has_aliases` 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./trivia-qa-lance/data") # local copy required for writes tbl = db.open_table("train") tbl.add_columns({ "question_length": "length(question)", "num_aliases": "array_length(answer_aliases)", "has_aliases": "array_length(answer_aliases) > 0", }) ``` If the values you want to attach already live in another table (offline reader-model predictions, alternate embeddings, retrieval scores from a different system), merge them in by joining on `question_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa scores = pa.table({ "question_id": pa.array(["tc_1", "tc_2"]), "retriever_score": pa.array([0.88, 0.31]), }) tbl.merge(scores, 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 (e.g., running a different embedding model over the questions), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 closed-book QA model the natural projection is the question, the canonical answer, and the alias list (the aliases serve as additional supervision targets during loss computation or evaluation); for a retriever or reranker on top of frozen features, project the precomputed embedding instead. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/trivia-qa-lance/data") tbl = db.open_table("train") train_ds = Permutation.identity(tbl).select_columns( ["question", "answer_value", "answer_aliases"] ) loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) for batch in loader: # batch carries only the projected columns; question_emb stays on disk. # tokenize question and answer, forward, backward... ... ``` Switching feature sets is a configuration change: passing `["question_emb", "answer_value"]` to `select_columns(...)` on the next run reads only the 384-d vectors and the canonical answer string, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/trivia-qa-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./trivia-qa-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} 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 scores or alternate embeddings 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 answers, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/trivia-qa-lance/data") remote_tbl = remote_db.open_table("train") batches = ( remote_tbl.search() .where("answer_type = 'WikipediaEntity' AND length(question) >= 60") .select( ["question_id", "question", "answer_value", "answer_aliases", "normalized_answer", "answer_type", "question_emb"] ) .to_batches() ) local_db = lancedb.connect("./trivia-qa-wiki") local_db.create_table("train", batches) ``` The resulting `./trivia-qa-wiki` 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/trivia-qa-lance/data` for `./trivia-qa-wiki`. ## Source & license Converted from [`mandarjoshi/trivia_qa`](https://huggingface.co/datasets/mandarjoshi/trivia_qa) (`rc.nocontext` config). TriviaQA is released under the Apache 2.0 license. ## Citation ``` @article{joshi2017triviaqa, title={TriviaQA: A Large Scale Distantly Supervised Challenge Dataset for Reading Comprehension}, author={Joshi, Mandar and Choi, Eunsol and Weld, Daniel S and Zettlemoyer, Luke}, journal={arXiv preprint arXiv:1705.03551}, year={2017} } ``` # VQAv2 Source: https://docs.lancedb.com/datasets/vqav2 A Lance-formatted version of VQAv2 — open-ended visual question answering on COCO images — sourced from lmms-lab/VQAv2. Each row is one (image, question, 10 annotator answers) triple with paired CLIP image and question embeddings drawn from the… Source dataset card and downloadable files for `lance-format/vqav2-lance`. A Lance-formatted version of [VQAv2](https://visualqa.org/) — open-ended visual question answering on COCO images — sourced from [`lmms-lab/VQAv2`](https://huggingface.co/datasets/lmms-lab/VQAv2). Each row is one `(image, question, 10 annotator answers)` triple with paired CLIP image and question embeddings drawn from the same shared space, plus the VQAv2 `question_type` / `answer_type` taxonomy and the consensus `multiple_choice_answer` — all available directly from the Hub at `hf://datasets/lance-format/vqav2-lance/data`. ## Key features * **Inline JPEG bytes** in the `image` column — no sidecar files or image folders. * **Paired CLIP embeddings in the same row** — `image_emb` and `question_emb` (OpenCLIP ViT-B/32, 512-dim, cosine-normalized) — so cross-modal text→image retrieval and question-similarity retrieval both work as a single indexed lookup. * **Both raw and consensus answers** — the 10 annotator answers in `answers` alongside the canonical `multiple_choice_answer`, with parallel `answer_confidences`. * **Pre-built ANN, FTS, scalar, and bitmap indices** covering both embeddings, the question text, the answer taxonomy, and the COCO and VQAv2 ids. ## Splits | Split | Rows | | ------------------ | ------- | | `validation.lance` | 214,354 | The `lmms-lab/VQAv2` redistribution declares only the eval splits in its `dataset_info`, so the train shards (\~444 k rows) are not bundled here today; they can be enabled by reading `data/train-*.parquet` directly with PyArrow or by switching to `Multimodal-Fatima/VQAv2_train`. Track progress in `TRACKED_DATASETS.md`. ## Schema | Column | Type | Notes | | ------------------------ | ------------------------------- | ----------------------------------------------------------- | | `id` | `int64` | Row index within split | | `image` | `large_binary` | Inline JPEG bytes | | `image_id` | `int64` | COCO image id | | `question_id` | `int64` | VQAv2 question id | | `question` | `string` | Natural-language question | | `question_type` | `string` | First few tokens of the question (e.g. `what is`, `is the`) | | `answer_type` | `string` | One of `yes/no`, `number`, `other` | | `multiple_choice_answer` | `string` | Canonical (most-common) answer | | `answers` | `list` | 10 annotator answers | | `answer_confidences` | `list` | Parallel confidence list (`yes` / `maybe` / `no`) | | `image_emb` | `fixed_size_list` | OpenCLIP image embedding (cosine-normalized) | | `question_emb` | `fixed_size_list` | OpenCLIP text embedding of the question (cosine-normalized) | Because both embeddings come from the same CLIP model, they share an embedding space and cross-modal retrieval (image→question or question→image) works without any additional alignment. ## 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` — keyword and hybrid search * `BITMAP` on `question_type`, `answer_type` — fast categorical filters over the VQAv2 taxonomy * `BTREE` on `image_id`, `question_id`, `multiple_choice_answer` — fast lookup by id and canonical answer ## 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import datasets hf_ds = datasets.load_dataset("lance-format/vqav2-lance", split="validation", streaming=True) for row in hf_ds.take(3): print(row["question"], "->", row["multiple_choice_answer"]) ``` ## Load with LanceDB LanceDB is the embedded retrieval library built on top of the Lance format ([docs](https://lancedb.com/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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") tbl = db.open_table("validation") 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lance ds = lance.dataset("hf://datasets/lance-format/vqav2-lance/data/validation.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: > > ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} > hf download lance-format/vqav2-lance --repo-type dataset --local-dir ./vqav2-lance > ``` > > Then point Lance or LanceDB at `./vqav2-lance/data`. ## Search The bundled `IVF_PQ` index on `image_emb` makes cross-modal text→image retrieval a single call: encode a question with the same OpenCLIP model used at ingest (ViT-B/32 `laion2b_s34b_b79k`, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") tbl = db.open_table("validation") seed = ( tbl.search() .select(["question_emb", "question", "multiple_choice_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", "multiple_choice_answer", "answer_type"]) .limit(10) .to_list() ) print("query question:", seed["question"], "->", seed["multiple_choice_answer"]) for r in hits: print(f" {r['image_id']:>12} [{r['answer_type']}] {r['question'][:60]} -> {r['multiple_choice_answer']}") ``` Because the CLIP embeddings are cosine-normalized, cosine is the right metric. Swap `vector_column_name="image_emb"` for `question_emb` to do question→question retrieval against the validation set instead, which is useful for clustering paraphrases or spotting near-duplicate questions across COCO images. Because the dataset also ships an `INVERTED` index on `question`, 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 "dog" must appear in the question text but you still want CLIP to handle visual similarity over the candidate set. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} hybrid_hits = ( tbl.search(query_type="hybrid", vector_column_name="image_emb") .vector(seed["question_emb"]) .text("dog") .select(["image_id", "question", "multiple_choice_answer"]) .limit(10) .to_list() ) for r in hybrid_hits: print(f" {r['image_id']:>12} {r['question'][:60]} -> {r['multiple_choice_answer']}") ``` Tune `metric`, `nprobes`, and `refine_factor` on the vector side to trade recall against latency. ## Curate A typical curation pass for VQAv2 combines a structural filter on the answer taxonomy (e.g. only yes/no questions, or only counting questions) with a content predicate on the question text or the consensus answer, so the candidate set is both categorically uniform and topically focused. 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") tbl = db.open_table("validation") candidates = ( tbl.search() .where( "answer_type = 'yes/no' AND question_type = 'is the' AND multiple_choice_answer IN ('yes', 'no')", prefilter=True, ) .select(["question_id", "image_id", "question", "multiple_choice_answer"]) .limit(500) .to_list() ) print(f"{len(candidates)} 'is the' yes/no candidates; first: {candidates[0]['question']} -> {candidates[0]['multiple_choice_answer']}") ``` The result is a plain list of dictionaries, ready to inspect, persist as a manifest of `question_id`s, or feed into the Evolve and Train workflows below. The `image` and embedding columns are never read, so the network traffic for a 500-row candidate scan is dominated by question and answer strings rather than JPEG bytes or vectors. ## 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, a `num_answer_tokens` count, and a `question_length` integer, 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 split first. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("./vqav2-lance/data") # local copy required for writes tbl = db.open_table("validation") tbl.add_columns({ "is_binary_answer": "multiple_choice_answer IN ('yes', 'no')", "question_length": "length(question)", "answer_length": "length(multiple_choice_answer)", "num_unique_answers": "array_length(answers)", }) ``` If the values you want to attach already live in another table (a model's predicted answer, an annotator-agreement score, or a difficulty rating), merge them in by joining on `question_id`: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa predictions = pa.table({ "question_id": pa.array([262148000, 262148001], type=pa.int64()), "model_answer": pa.array(["yes", "2"]), "model_confidence": pa.array([0.87, 0.64]), }) 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 (e.g., running an alternate VLM over the image bytes), Lance provides a batch-UDF API — see the [Lance data evolution docs](https://lance.org/guide/data_evolution/). ## 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 consensus answer; columns added in the Evolve section above cost nothing per batch until they are explicitly projected. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.permutation import Permutation from torch.utils.data import DataLoader db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") tbl = db.open_table("validation") train_ds = Permutation.identity(tbl).select_columns(["image", "question", "multiple_choice_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 # against `multiple_choice_answer`... ... ``` Switching feature sets is a configuration change: passing `["image_emb", "question_emb", "multiple_choice_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 training a lightweight reranker or a linear probe on top of 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") tbl = db.open_table("validation") 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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} local_db = lancedb.connect("./vqav2-lance/data") local_tbl = local_db.open_table("validation") local_tbl.tags.create("openclip-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: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl_v1 = db.open_table("validation", version="openclip-vitb32-v1") tbl_v5 = db.open_table("validation", version=5) ``` Pinning supports two workflows. A retrieval system locked to `openclip-vitb32-v1` keeps returning stable results while the dataset evolves in parallel — newly added model predictions or alternative 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, questions, and consensus answers, 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. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb remote_db = lancedb.connect("hf://datasets/lance-format/vqav2-lance/data") remote_tbl = remote_db.open_table("validation") batches = ( remote_tbl.search() .where("answer_type = 'number'") .select(["question_id", "image_id", "image", "question", "multiple_choice_answer", "answers", "image_emb", "question_emb"]) .to_batches() ) local_db = lancedb.connect("./vqav2-counting-subset") local_db.create_table("validation", batches) ``` The resulting `./vqav2-counting-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/vqav2-lance/data` for `./vqav2-counting-subset`. ## Source & license Converted from [`lmms-lab/VQAv2`](https://huggingface.co/datasets/lmms-lab/VQAv2). VQAv2 questions and annotations are released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The underlying images come from COCO and are subject to Flickr terms of service. See the [VQAv2 download page](https://visualqa.org/download.html) for details. ## Citation ``` @inproceedings{goyal2017making, title={Making the V in VQA Matter: Elevating the Role of Image Understanding in Visual Question Answering}, author={Goyal, Yash and Khot, Tejas and Summers-Stay, Douglas and Batra, Dhruv and Parikh, Devi}, booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2017} } ``` # Demo Application Gallery Source: https://docs.lancedb.com/demos/index Demo apps showcasing end-to-end applications built with LanceDB for production use cases. Explore the demo applications built with LanceDB below. | App | Description | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | [Semantic.Art](#semantic-art) | A multimodal art discovery platform using feelings, phrases, and images. | | [Wikipedia 41M Hybrid Search](#wikipedia-41m-hybrid-search) | An interactive hybrid search demo combining full-text search and vector search. | | [Video Search](#video-search) | A video search application that allows searching through a library of videos using natural language queries. | ## Semantic.art multimodal hybrid-search vector-search semantic-routing Semantic.art turns real, human-made art discovery into a multimodal search experience using feelings, phrases, and images. It's built with LanceDB hybrid search and semantic routing. Read in detail about how Semantic.art is built in this blog post. ## Wikipedia 41M Hybrid Search multimodal hybrid-search vector-search fts Interactive hybrid search, full-text search (FTS) and vector search demo with 41M+ Wikipedia entries. Explore the power of combining FTS with vector search for more relevant results. Read in detail about how the Wikipedia 41M hybrid search demo is built in this blog post. ## Video Search multimodal video-search vector-search Search through a library of videos using natural language queries. This demo showcases how to use LanceDB to perform semantic search on video content. # Managing Embeddings Source: https://docs.lancedb.com/embedding/index Use the embedding API in LanceDB -- registry, functions, schemas, and multi-language SDK support. Modern machine learning models can be trained to convert raw data into embeddings, which are vectors of floating point numbers. The position of an embedding in vector space captures the semantics of the data, so vectors that are close to each other are considered similar. LanceDB provides an embedding function registry in OSS as well as its Enterprise versions ([see below](#embeddings-in-lancedb-enterprise)) that automatically generates vector embeddings during data ingestion. Automatic query-time embedding generation is available in LanceDB OSS, with SDK-specific query ergonomics. The API abstracts embedding generation, allowing you to focus on your application logic. ## Embedding Registry You can get a supported embedding function from the registry, and then use it in your table schema. Once configured, the embedding function will automatically generate embeddings when you insert data into the table. Query-time behavior depends on SDK: Python/TypeScript can query with text directly, while Rust examples typically compute query embeddings explicitly before vector search. ### Using an embedding function Create an embedding function before you attach it to table or schema metadata. Python and TypeScript fetch provider implementations from the embedding registry, while Rust constructs the provider embedding function directly and registers it on the connection before using it in an `EmbeddingDefinition`. Provider configuration is SDK-specific, so copy the option names from the provider page for the SDK you use. For example, the OpenAI model is selected with `name` in Python, `model` in TypeScript, and the model argument to `OpenAIEmbeddingFunction::new_with_model` in Rust. | Concept | Python | TypeScript | Rust | | ----------- | -------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------- | | Model | `name="text-embedding-3-small"` | `{ model: "text-embedding-3-small" }` | `new_with_model(api_key, "text-embedding-3-small")` | | Retry count | `max_retries=7` | Provider/client-specific | Provider/client-specific | | API key | `api_key="..."`, environment variables, or `$var:` | `apiKey: "..."`, environment variables, or `$var:` | Constructor argument or environment variable | | Device | Provider-specific, for example `device="cuda"` | Provider-specific | Provider-specific | When ingesting data with an embedding definition, LanceDB only computes the vector column if that column is missing from the incoming batch or present but entirely null. If you provide any non-null values in the vector column, LanceDB treats the column as user-supplied and does not backfill the remaining rows in that batch. For reusable runtime configuration, the registry also supports `$var:` placeholders in embedding-function config. This is useful for provider secrets and environment-specific settings in Python and TypeScript. * Python uses `registry.set_var(...)`. * TypeScript uses `registry.setVar(...)`. * You can provide a fallback with `$var:name:default`. * Sensitive values such as API keys should be passed through registry variables instead of hardcoding them in config. For non-sensitive settings such as inference device selection, you can also use a default fallback: Find the full list of arguments for each provider in the [integrations](/integrations/embedding) section. ## Multiple embedding columns A single table can include more than one embedding definition when you want to store multiple semantic views of the same data, or generate embeddings from different source columns. In practice, each embedding definition maps one source column to one vector column, and the table schema can contain multiple such pairs. The exact setup differs by SDK, but the underlying pattern is the same: define a distinct source/vector pair for each embedding function you want applied during ingest. In TypeScript, automatic query embedding currently uses the first embedding function stored in the table metadata. If a table has multiple embedding definitions and you need to query a specific vector column, compute the query embedding explicitly and pass the vector to the search builder. ## Embedding model providers LanceDB supports most popular embedding providers. ### Text embeddings | Provider | Model ID | Default Model | | --------------------- | ----------------------- | ------------------------ | | OpenAI | `openai` | `text-embedding-ada-002` | | Sentence Transformers | `sentence-transformers` | `all-MiniLM-L6-v2` | | Hugging Face | `huggingface` | `colbert-ir/colbertv2.0` | | Cohere | `cohere` | `embed-english-v3.0` | | ... | ... | ... | ### Multimodal embedding | Provider | Model ID | Supported Inputs | | --------- | ----------- | -------------------------- | | OpenCLIP | `open-clip` | Text, Images | | ImageBind | `imagebind` | Text, Images, Audio, Video | | ... | ... | ... | You can find all supported embedding models in the [integrations](/integrations/embedding) section. ## Embeddings in LanceDB Enterprise Enterprise In LanceDB Enterprise, embedding generation during data ingestion is client-side and the resulting vectors are stored on the remote table. The Enterprise server does not currently generate embeddings from query text on its own. Any automatic query-time embedding happens on the client side. ### How string queries are interpreted For the Python remote client, `table.search("hello")` can take two different paths: * If the selected vector column has embedding metadata (i.e., the table schema stores the source-column, vector-column, and embedding-function mapping created from fields like `SourceField()` and `VectorField()` during table creation), then the embeddings are computed in the Python client process. The client uses the same local LanceDB embedding registry used by OSS tables to reconstruct the embedding function from schema metadata, compute the query vector in the client process, and send that vector to Enterprise for search. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} result = table.search("hello").limit(1).to_list() # The Python client computes the query embedding locally, then sends a vector search. ``` * If the table does not have embedding metadata for that search, `table.search("hello")` in `auto` mode is treated as an FTS query instead. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} result = table.search("hello").limit(5).to_list() # In auto mode this is treated as an FTS query, not a vector query. ``` If you want explicit vector or hybrid behavior and the client cannot resolve an embedding function from the table metadata, generate the query embedding yourself and pass the vector directly. TypeScript has a similar string-query distinction in `auto` mode: if no embedding providers have been registered in the process, `search("text")` falls back to FTS. Once an embedding provider has been imported and registered, the client expects table embedding metadata for automatic vector search and raises an error if the table has none. The manual query-embedding flow below works across Enterprise SDKs and is an explicit path you can use when you want full control over query-time behavior. ## Custom Embedding Functions You can always implement your own embedding function: * Python/TypeScript: subclass `TextEmbeddingFunction` (text) or `EmbeddingFunction` (multimodal). * Rust: implement the `EmbeddingFunction` trait. # Embeddings: Quickstart Source: https://docs.lancedb.com/embedding/quickstart Quickstart guide for generating and working with embeddings. LanceDB will automatically vectorize the data both at ingestion and query time. All you need to do is specify which model to use. Popular embedding models like OpenAI, Hugging Face, Sentence Transformers, CLIP, and more, are supported. ## Step 1: Import Required Libraries First, import the necessary LanceDB components: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.pydantic import LanceModel, Vector from lancedb.embeddings import get_registry ``` * `lancedb`: The main database connection and operations * `LanceModel`: Pydantic model for defining table schemas * `Vector`: Field type for storing vector embeddings * `get_registry()`: Access to the embedding function registry. It has all the supported as well as custom embedding functions registered by the user * TypeScript uses `lancedb.embedding.getRegistry()` and `lancedb.embedding.LanceSchema()` for the same registry/schema workflow * In TypeScript, import the provider module before calling `getRegistry().get(...)`; the provider import is what registers names such as `"huggingface"` or `"openai"` ## Step 2: Connect to LanceDB Establish a connection to your LanceDB OSS directory or Enterprise cluster: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Enter your LanceDB connection URI for OSS or Enterprise here db = lancedb.connect(...) ``` ## Step 3: Initialize the Embedding Function Choose and configure your embedding model: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} model = get_registry().get("sentence-transformers").create(name="BAAI/bge-small-en-v1.5", ) ``` This creates an embedding function from the local embedding registry. The Python snippet uses the `sentence-transformers` provider with the BGE model; the TypeScript snippet uses the Transformers-backed `huggingface` provider. You can: * Change `"sentence-transformers"` to other providers like `"openai"`, `"cohere"`, etc. * Modify the model name for different embedding models * Set `device="cuda"` for GPU acceleration if available ## Step 4: Define Your Schema Create a Pydantic model that defines your table structure: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} class Words(LanceModel): text: str = model.SourceField() vector: Vector(model.ndims()) = model.VectorField() ``` * `SourceField()`: This field will be embedded * `VectorField()`: This stores the embeddings * `model.ndims()`: Sets vector dimensions for your model * In TypeScript, use `model.sourceField(...)` and `model.vectorField()` inside `LanceSchema(...)` ## Step 5: Create Table and Ingest Data Create a table with your schema and add data: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} table = db.create_table("words", schema=Words) table.add([ {"text": "hello world"}, {"text": "goodbye world"} ]) ``` The `table.add()` call automatically: * Takes the text from each document * Generates embeddings using your chosen model * Stores both the original text and the vector embeddings If your input already includes the vector column, automatic embedding only runs when that column is absent or entirely null for the batch. Partially supplied vectors are treated as manual data, so LanceDB preserves them instead of filling only the missing rows. ## Step 6: Query with Automatic Embedding Note: On LanceDB Enterprise, the server does not generate embeddings from query text. In the Python remote client, `table.search("greetings")` can still work when the table schema includes embedding metadata, because the client computes the query embedding before sending the vector search. If there is no embedding metadata for that search, `search("greetings")` in `auto` mode is treated as FTS instead. Search your data using natural language queries: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} query = "greetings" actual = table.search(query).limit(1).to_pydantic(Words)[0] print(actual.text) ``` The search process: 1. Automatically converts your query text to embeddings 2. Finds the most similar vectors in your table 3. Returns the matching documents Automatic text search depends on the table's embedding metadata. If the client cannot reconstruct an embedding function from that metadata, compute the query embedding yourself and search with the vector directly. # Architecture Source: https://docs.lancedb.com/enterprise/architecture Learn how LanceDB Enterprise separates control, compute, and storage to serve remote tables at scale. LanceDB Enterprise is a remote, cluster-backed service built for teams that need low-latency search, predictable operations, and durable storage beyond a single machine. Instead of tying query serving, indexing, compaction, and data persistence to the same process, Enterprise separates those concerns so each part of the system can scale and evolve independently. At a high level, it helps to think of LanceDB Enterprise as a set of layers that interoperate with one another. Users connect to **remote tables** over the network. The **data plane** serves reads, writes and background jobs such as indexing and compaction. The **control plane** manages configuration, identity, policy, and cluster lifecycle. **Indexers** build indexes and compact data outside the request path. **Object storage** holds the durable table data, manifests, and index artifacts independently of the machines serving traffic. This architecture matters because enterprise workloads are rarely shaped like a single benchmark. Some need thousands of concurrent queries. Others need large-scale ingestion, continuous indexing, or strict operational boundaries between user traffic and background work. LanceDB Enterprise is designed so those workloads do not all compete for the same machine, disk, or process. ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} flowchart TB RT[Remote tables] subgraph DP[Data plane] QS[Query serving] IX[Indexers] end CP[Control plane] OS[(Object storage)] RT --> DP DP --> OS IX --> OS CP -.->|Govern and configure| DP CP -.->|Govern and configure| IX ``` ## Compute-storage separation In LanceDB Enterprise, storage and compute are deliberately decoupled. Table data and index artifacts live in object storage, while query-serving and background workers read from and write to that shared durable layer. This means compute can be replaced, scaled, or specialized without making any individual node the owner of the dataset. This design has practical consequences. Query fleets can scale for interactive traffic without also scaling background indexing capacity. Heavy indexing and compaction work can run on dedicated workers instead of stealing resources from user-facing queries. Caches can accelerate hot reads without becoming the source of truth. And because the data remains in object storage, durability does not depend on the lifecycle of a particular server or local disk. ## Architecture At a high level, the control plane governs the system and the data plane executes the work. The control plane is responsible for configuration, service discovery, identity integration, policy, and cluster lifecycle. It determines how the system should behave, but it is not the layer serving table data or executing user queries -- that's the role of the data plane. Within the data plane, serving is separated into two kinds of nodes that are provisioned independently. Query nodes are the client-facing layer. They receive requests against remote tables, validate them, resolve the target table, plan the work, and return results. Plan executors are the read-execution layer behind the query nodes. For read-heavy query paths, they execute cache-backed reads against object storage, which helps reduce repeated remote reads and makes performance more predictable as load grows. Indexers handle heavyweight background work such as building indexes, merging index state, compacting data, and updating the table’s stored index and layout artifacts. Together, these components let LanceDB Enterprise scale request handling, read execution, and index-building independently instead of forcing them to compete for the same compute resources. ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} flowchart TB Client[Client applications] subgraph CP[Control plane] CFG[Configuration, identity, policy, lifecycle] end subgraph DP[Data Plane
Managed or self-hosted] QP[Query planning + routing fleet] PE[Executor + cache fleet] MON[Monitoring + orchestration fleet] WAL[MemWAL fleet] JOBS[Jobs fleet] QP -->|Query subplans| PE QP -->|Send state + job requests| MON QP -->|Fresh reads and writes| WAL PE -.->|Send state| MON WAL -.->|Send state| MON MON -->|Indexing, compaction,
backfill, refresh jobs| JOBS JOBS -->|Send state| MON end OS[(Object Storage)] Client -->|Queries, writes, backfills| DP CFG -.->|Govern and configure| DP PE -->|Read, cache data| OS JOBS -->|Read/write table and feature data| OS MON -.->|Table and cluster state| OS WAL -->|Persist| OS ``` ## Remote tables A [remote table](/tables-and-namespaces#understanding-tables) is the user-facing abstraction over this architecture. From the client side, you connect to a logical storage layer and table over the network by providing a `db://...` connection identifier. The system then resolves that logical name to the underlying storage-backed table and executes the operation inside the cluster. This is why Enterprise feels familiar at the API level while operationally behaving differently. Your application still issues table operations and queries, but it is no longer coupled to a local storage path or a single host. Instead, the cluster takes responsibility for execution, coordination, and background upkeep. In SDK terms, `open_table(...)` returns a `RemoteTable`. Architecturally, a remote table is the bridge between the client-facing API and the storage-backed system behind it. This design makes LanceDB Enterprise suitable for catalog-backed layouts, see [Namespaces and the Catalog Model](/namespaces) for more details. For the basic application flow, see the shared [quickstart](/quickstart). ## Read path When a client issues a query against a remote table, the path is straightforward: 1. The request reaches a query node in the data plane. 2. The query node validates the request, resolves the table, and plans the work. 3. For read-heavy queries, the query node can send part of the read work to plan executors. 4. Plan executors read the required table data from object storage, using cache where it helps reduce repeated remote reads. 5. Results are returned to the query node, assembled, and sent back to the client. This separation is what lets Enterprise combine a clean remote API with a serving layer that can scale horizontally and keep hot data close to execution. ## Write path Writes follow a different path because durability comes first: 1. A client writes to a remote table. 2. The query node validates the request and commits the new table state. 3. After the write succeeds, the system emits follow-up signals for indexing, compaction, or cleanup. Keeping the commit path centered on object storage ensures that the durable record of the table lives outside any single query node. Regardless of whether you're using Lance namespaces or an external catalog, the catalog's role is mainly to resolve table names and provide access details -- the table’s actual data and index artifacts remain in object storage. ## Background work Indexing, compaction, and cleanup are intentionally moved off the user request path. After table changes are committed, the system can determine that additional work is needed and assign it to background workers built for heavyweight processing. In practice, that usually looks like this: 1. Table changes create follow-up events that query nodes publish to an event queue for indexing, compaction, or cleanup. 2. A background coordinator agent consumes those events, evaluates the state of the table, and decides what should run next. 3. Indexers read the relevant table state from object storage, produce updated artifacts, and write the results back. This separation is one of the clearest architectural reasons to use LanceDB Enterprise: the same query-serving infrastructure does not have to handle every expensive indexing or compaction task itself. ## What this means for users For teams using LanceDB Enterprise, the architecture changes the *operational model* more than the programming model. You still work with tables and queries, but the cluster now takes responsibility for distributed execution, cache-aware reads, and long-running background jobs. The result is a system that is easier to run under production pressure: * You interact with remote tables instead of managing physical storage layout directly. * Query serving, indexing, and compaction can scale independently. * Durable state lives in object storage rather than on individual machines. * Background indexing and compaction improve performance over time without forcing that work into the foreground request path. This architecture gives ML and AI teams a strong storage-backed platform for training, retrieval, search, and analytics workloads that can scale beyond a single machine while still providing a familiar API and predictable performance. # Authentication Source: https://docs.lancedb.com/enterprise/authentication Authentication modes for LanceDB Enterprise with an API key or OAuth credentials. LanceDB Enterprise supports two ways for clients to authenticate against a `db://` remote table: * **API keys** — a long-lived shared secret passed as `api_key` on connect. Works in every SDK. * **OAuth 2.0** — short-lived bearer tokens obtained from your identity provider, refreshed automatically by the client. OAuth is the recommended option when you want to rotate credentials centrally, plug into an existing identity provider, or run on Azure with managed identities so no secret material lives on the client. ## API key Pass the API key from your Enterprise tenant on `connect`. This works with both the synchronous and asynchronous Python clients, as well as TypeScript and Rust. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect( uri="db://your-database-uri", api_key="your-api-key", region="us-east-1", host_override="https://your-enterprise-endpoint.com", ) ``` ## OAuth The async Python client and the TypeScript client can obtain bearer tokens from an OIDC issuer and attach them to every request. Token acquisition, caching, and refresh are handled inside the client — your application code only provides the configuration. In Python, OAuth is supported through `lancedb.connect_async`. The synchronous `connect` entry point continues to use API key authentication for `db://` URIs. In TypeScript, `lancedb.connect` accepts `oauthConfig` directly. ### Supported flows `OAuthFlowType` selects how the client acquires tokens: | Flow | Python value | TypeScript value | When to use | | ---------------------- | -------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Client Credentials | `OAuthFlowType.CLIENT_CREDENTIALS` | `OAuthFlowType.ClientCredentials` | Service-to-service / machine-to-machine. Requires a client ID and client secret registered with your identity provider. | | Azure Managed Identity | `OAuthFlowType.AZURE_MANAGED_IDENTITY` | `OAuthFlowType.AzureManagedIdentity` | Workloads running on Azure compute (VMs, AKS, App Service, Container Apps). Tokens are fetched from the Azure IMDS endpoint, so no client secret is stored on the client. | ### Configure OAuth Build an OAuth config and pass it on connect. Use `oauth_config` in Python and `oauthConfig` in TypeScript. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb from lancedb.remote import OAuthConfig, OAuthFlowType # Client Credentials (service-to-service) oauth_config = OAuthConfig( issuer_url="https://login.microsoftonline.com/{tenant}/v2.0", client_id="your-application-client-id", client_secret="your-application-client-secret", scopes=["api://lancedb-api/.default"], flow=OAuthFlowType.CLIENT_CREDENTIALS, ) db = await lancedb.connect_async( uri="db://your-database-uri", region="us-east-1", host_override="https://your-enterprise-endpoint.com", oauth_config=oauth_config, ) ``` ```typescript TypeScript icon="square-js" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import * as lancedb from "@lancedb/lancedb"; import { OAuthConfig, OAuthFlowType } from "@lancedb/lancedb"; // Client Credentials (service-to-service) const oauthConfig: OAuthConfig = { issuerUrl: "https://login.microsoftonline.com/{tenant}/v2.0", clientId: "your-application-client-id", clientSecret: "your-application-client-secret", scopes: ["api://lancedb-api/.default"], flow: OAuthFlowType.ClientCredentials, }; const db = await lancedb.connect("db://your-database-uri", { region: "us-east-1", hostOverride: "https://your-enterprise-endpoint.com", oauthConfig, }); ``` For workloads running on Azure compute, use a managed identity instead of a client secret: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from lancedb.remote import OAuthConfig, OAuthFlowType oauth_config = OAuthConfig( issuer_url="https://login.microsoftonline.com/{tenant}/v2.0", client_id="your-application-client-id", scopes=["api://lancedb-api/.default"], flow=OAuthFlowType.AZURE_MANAGED_IDENTITY, # Optional: required only for user-assigned managed identities. managed_identity_client_id="your-user-assigned-identity-client-id", ) ``` ```typescript TypeScript icon="square-js" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import { OAuthConfig, OAuthFlowType } from "@lancedb/lancedb"; const oauthConfig: OAuthConfig = { issuerUrl: "https://login.microsoftonline.com/{tenant}/v2.0", clientId: "your-application-client-id", scopes: ["api://lancedb-api/.default"], flow: OAuthFlowType.AzureManagedIdentity, // Optional: required only for user-assigned managed identities. managedIdentityClientId: "your-user-assigned-identity-client-id", }; ``` ### Configuration reference The same configuration is available in both SDKs. Python uses `snake_case` field names; TypeScript uses `camelCase`. | Python field / TypeScript field | Required | Description | | -------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `issuer_url` / `issuerUrl` | Yes | OIDC issuer URL or OAuth authority URL. For Azure, use `https://login.microsoftonline.com/{tenant_id}/v2.0`. | | `client_id` / `clientId` | Yes | Application / client ID registered with your identity provider. | | `scopes` | Yes | List of OAuth scopes to request. For Azure managed identity, provide exactly one scope or resource, for example `["api://your-app-id/.default"]`. | | `flow` | No | Selects the OAuth flow. Defaults to Client Credentials. | | `client_secret` / `clientSecret` | Conditional | Required for Client Credentials. Redacted from the config's `repr` (Python) and `Debug` output (TypeScript native binding) so it does not leak into logs. | | `managed_identity_client_id` / `managedIdentityClientId` | No | Client ID of a user-assigned managed identity. Only used with Azure Managed Identity; omit for system-assigned identities. | | `refresh_buffer_secs` / `refreshBufferSecs` | No | How many seconds before token expiry to proactively refresh. Defaults to 300. Keep this well below the token TTL — setting it greater than or equal to the TTL forces a refresh on every request. | Treat the client secret like any other production credential. Load it from a secret manager or environment variable rather than committing it to source control. Both SDKs deliberately omit the secret from their debug/repr output so accidental log lines do not expose the value. # Benchmarks Source: https://docs.lancedb.com/enterprise/benchmarks Representative latency and scalability benchmarks for LanceDB Enterprise. LanceDB Enterprise is designed for low-latency, high-throughput search, but observed performance depends on factors such as dataset shape, index configuration, cache warmth, filter selectivity, concurrency, and cluster sizing. The figures on this page should be read as representative benchmark results for a specific test setup, not as universal guarantees for every deployment. In our benchmark environment, warmed-cache vector search reached around **25ms** P50 latency, and metadata-filtered search reached around **50ms** P99 latency for the filter pattern shown below. If you want performance guidance for your own workload, reach out to [contact@lancedb.com](mailto:contact@lancedb.com). The LanceDB engineering team can help map your latency, throughput, ingestion, and filtering requirements to an appropriate Enterprise cluster design. | Percentile | Vector Search | Vector Search w. Filtering | Full-Text Search | | :--------: | :-----------: | :------------------------: | :--------------: | | P50 | 25ms | 30ms | 26ms | | P90 | 26ms | 39ms | 37ms | | P99 | 35ms | 50ms | 42ms | Depending on workload and tuning, Enterprise clusters can also be configured for high concurrency, including thousands of QPS in some deployments, but the right configuration varies by use case. Training, search, and analytics workloads often benefit from different cluster shapes and resource allocation strategies. To understand which parts of the system influence these results, see the [Enterprise architecture](/enterprise/architecture) guide. ## Dataset We used two datasets for this benchmark: the [dbpedia-entities-openai-1M](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) for vector search, and a synthetic dataset for vector search with metadata filtering. | Name | # Vectors | Vector Dimension | | :------------------------- | :--------: | :--------------: | | dbpedia-entities-openai-1M | 1,000,000 | 1536 | | synthetic dataset | 15,000,000 | 256 | These benchmark results are most useful as a directional baseline. Different data distributions, index choices, cache behavior, and cluster settings can materially change the latency profile. ## Vector Search We ran vector queries against `dbpedia-entities-openai-1M` with a warmed-up cache. In that benchmark setup, we observed the following latency profile: | Percentile | Latency | | :--------: | :-----: | | P50 | 25ms | | P90 | 26ms | | P99 | 35ms | | Max | 49ms | ## Full-Text Search With the same dataset and a warmed-up cache, full-text search fell into the following range: | Percentile | Latency | | :--------: | :-----: | | P50 | 26ms | | P90 | 37ms | | P99 | 42ms | | Max | 98ms | ## Vector Search with Metadata Filtering We created a 15M-vector dataset to evaluate metadata-aware search under more complex filtering conditions. These filters can span a wide range of scalar columns, for example, "find Sci-fi movies since 1900". With a warmed-up cache, slightly more selective filters, for example, "find Sci-fi movies between the years 2000 and 2012", produced the following representative results: | Percentile | Latency | | :--------: | :-----: | | P50 | 30ms | | P90 | 39ms | | P99 | 50ms | Broader or less selective filters, for example, "find Sci-fi movies since 1900", moved the latency range to: | Percentile | Latency | | :--------: | :-----: | | P50 | 65ms | | P90 | 76ms | | P99 | 100ms | These benchmarks are intended to provide consistent, reproducible reference points for LanceDB Enterprise rather than one-size-fits-all promises. We periodically re-run and update numbers as necessary, but production performance will still depend on workload shape, cluster tuning, and the architectural choices described in the [Enterprise architecture](/enterprise/architecture) guide. # Azure deployment guide Source: https://docs.lancedb.com/enterprise/deployment/azure Learn how to deploy LanceDB Enterprise on Azure with AKS, Private Link, and Blob Storage. LanceDB Enterprise can be deployed on Azure using Azure Kubernetes Service (AKS) with Azure Blob Storage for data persistence and Azure Private Link for secure connectivity. ## General Architecture Overview ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} graph TB subgraph "Client VPC" Client[Client Applications] end subgraph "Server VPC" PLS[Azure Private Link Service] subgraph "AKS Cluster" LDB[LanceDB Enterprise
Query Nodes, Plan Executors,
Lance Agent, Indexer Pods] end EH[Azure EventHub
for LanceDB internal
message passing] BS[Azure Blob Storage] WI[Azure Workload Identity] end Client ==>|Private Link| PLS PLS ==> LDB LDB <-->|Read/Write| BS LDB -->|Async Events| EH EH -->|Process| LDB WI -.->|RBAC| BS WI -.->|Assigned| LDB style Client fill:#d7e3fc,stroke:#5c6bc0,stroke-width:2px,color:#0d1b2a style PLS fill:#f3e5f5,stroke:#ab47bc,stroke-width:2px,color:#311432 style LDB fill:#ffe0b2,stroke:#fb8c00,stroke-width:2px,color:#4a2f11 style EH fill:#f8bbd0,stroke:#ec407a,stroke-width:2px,color:#4a0821 style BS fill:#e0f2f1,stroke:#26a69a,stroke-width:2px,color:#09312d style WI fill:#e6f4ea,stroke:#66bb6a,stroke-width:2px,color:#1d3a1f ``` ### Key Components * **LanceDB architecture** is deployed in an AKS cluster within its own VPC * **Client applications** connect to the cluster securely using Azure Private Link * **AKS cluster** is granted Azure Blob Storage read/write permissions using Azure Workload Identity * **Azure EventHub** can be used as the message queue by LanceDB Enterprise for internal message communication (alternative: self-hosted Kafka cluster in AKS) ## Read Path Architecture ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} graph LR subgraph "Client Network" C[Client App] end subgraph "Azure AKS Cluster" PL[Private Link
Service] QN[Query Nodes
Phalanx] PE[Plan Executors
Distributed Data Cache] end subgraph "Storage" BS[Azure Blob
Storage] end C -->|Private
Connection| PL PL --> QN QN -->|Query
Request| PE PE -->|Cache Miss
Read Data| BS style C fill:#d7e3fc,stroke:#5c6bc0,color:#0d1b2a style PL fill:#f3e5f5,stroke:#ab47bc,color:#311432 style QN fill:#ffe0b2,stroke:#fb8c00,color:#4a2f11 style PE fill:#ffecb3,stroke:#ffb74d,color:#4a2f11 style BS fill:#e0f2f1,stroke:#26a69a,color:#09312d ``` ### Read Path Flow 1. **Client Application** sends query request through Private Link 2. **Query Nodes** receive and process the request 3. **Plan Executors** optimize and execute the query using distributed data cache to speed up read queries 4. **Azure Blob Storage** stores data and indices in Lance, while Plan Executors maintain distributed cache for performance ## Write Path Architecture ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} graph LR subgraph "Client Network" C[Client App] end subgraph "Azure AKS Cluster" PL[Private Link
Service] QN[Query Nodes
Phalanx] LA[Lance Agent] IP[Indexer Pods
On-Demand] end subgraph "Messaging" EH[Azure EventHub
Write Events] end subgraph "Storage" BS[Azure Blob
Storage] end C -->|Private
Connection| PL PL --> QN QN -->|Sync
Write| BS QN -->|Async
Events| EH EH -->|Consume| LA LA -->|Launch| IP IP -->|Index &
Optimize| BS style C fill:#d7e3fc,stroke:#5c6bc0,color:#0d1b2a style PL fill:#f3e5f5,stroke:#ab47bc,color:#311432 style QN fill:#ffe0b2,stroke:#fb8c00,color:#4a2f11 style LA fill:#ffe5c3,stroke:#ffb74d,color:#4a2f11 style IP fill:#ffe5c3,stroke:#ffb74d,color:#4a2f11 style EH fill:#f8bbd0,stroke:#ec407a,color:#4a0821 style BS fill:#e0f2f1,stroke:#26a69a,color:#09312d ``` ### Write Path Flow Query nodes write data and indices synchronously to Azure Blob Storage in Lance data format while asynchronously sending data modification events to Azure EventHub (or self-hosted Kafka cluster). These write events are processed by the Lance Agent, which launches indexing pods or data optimization pods to optimize data for better read performance. ## Deployment Options ### Storage Architecture Support ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} graph TB subgraph "Multi-Account & Multi-Container Support" SA1[Storage Account 1] SA2[Storage Account 2] SA3[Storage Account N] SA1 --> C1A[Container A] SA1 --> C1B[Container B] SA1 --> C1C[Container C] SA2 --> C2A[Container X] SA2 --> C2B[Container Y] SA3 --> C3A[Container 1] SA3 --> C3B[Container 2] end style SA1 fill:#e0f2f1,stroke:#26a69a,color:#09312d style SA2 fill:#e0f2f1,stroke:#26a69a,color:#09312d style SA3 fill:#e0f2f1,stroke:#26a69a,color:#09312d ``` ### Deployment Models LanceDB Enterprise supports three deployment models on Azure: #### 1. Fully Managed Service * **Infrastructure and storage** in LanceDB's Azure account * **Complete management** by LanceDB team * **Simplest setup** for customers #### 2. BYOC (Bring Your Own Cloud) * **Infrastructure and storage** in customer's Azure account * **Fully Managed by LanceDB** * **Full control** over data residency #### 3. Hybrid - Bring Your Own Container * **Infrastructure** in LanceDB's account * **Storage containers** in customer's account For private deployments, high performance at extreme scale, or if you have strict security requirements, [contact us about LanceDB Enterprise](mailto:contact@lancedb.com). # Deployment guide Source: https://docs.lancedb.com/enterprise/deployment/index Learn how to deploy LanceDB Enterprise in production environments. There are two deployment models available for LanceDB Enterprise: **Managed** and **BYOC**. Both models support AWS, GCP, and Azure cloud platforms. ## Managed deployment This is a private deployment of LanceDB Enterprise. All applications run in cloud accounts managed by LanceDB in the same location as your client applications. This hands-off approach is recommended for users who do not wish to manage the infrastructure themselves. To access your deployment, LanceDB can provision either a public or private load balancer. ## Bring-your-own-cloud (BYOC) deployment With this deployment model, LanceDB Enterprise is installed into your own cloud account. This approach is recommended when: * Users' security requirements for data residency preclude them from having data leave their account * Other applications need to access the object storage directly To deploy, an identity will be provisioned in your account with permissions to manage the infrastructure. ## Custom deployments LanceDB Enterprise installation is highly configurable and customizable to your needs. If you have any other specific deployment requirements, please reach out to our support team at [contact@lancedb.com](mailto:contact@lancedb.com). # LanceDB Enterprise Source: https://docs.lancedb.com/enterprise/index Features and benefits of LanceDB Enterprise. **LanceDB Enterprise** is the production deployment option for teams that want to run LanceDB as a private cloud or bring-your-own-cloud (BYOC) **multimodal lakehouse**. If you are new to multimodal lakehouses, the short version is this: LanceDB keeps vectors, metadata, and source data together in open table storage, while Enterprise adds the distributed infrastructure needed to serve real production workloads on top of that data. It is designed for teams that need more scale, more operational visibility, and more control than a single embedded process can provide. If you need private deployments, high performance at extreme scale, or if you have strict security requirements, [reach out to our team](mailto:contact@lancedb.com) to set up a LanceDB Enterprise cluster in your environment. ## Why use LanceDB Enterprise? If you are evaluating LanceDB for a production AI system, Enterprise is built around three practical needs: handling very large vector workloads, running feature engineering close to the data, and operating the platform with production visibility. ### 1. 100B+ row scale LanceDB Enterprise is built for demanding workloads that exceed the capabilities of a single machine, whether from extremely large data volumes or a high number of concurrent queries. Instead of asking your application to own caching, query scaling, and maintenance, Enterprise turns those into **platform** capabilities. This matters when your AI application moves past a prototype and starts serving real users, larger datasets, and more concurrent requests. * **Low-latency tiered cache**: Enterprise keeps frequently read data closer to compute, so common queries do not need to fetch the same data from object storage over and over again. That helps reduce wait times and makes performance more predictable as traffic increases. * **Horizontal query throughput**: Instead of relying on one application process to answer every query, Enterprise can spread search traffic across multiple nodes. This lets teams add capacity as usage grows, rather than re-architecting the application each time demand spikes. * **Distributed search**: Coming soon. Enterprise is adding dynamic horizontal scaling to search execution to allow for low latency search at much higher volumes and concurrency. * **Distributed indexing and compaction**: Coming soon. Enterprise is expanding support for large-table maintenance so indexing and storage cleanup can happen as platform workflows rather than as manual operator tasks. * **Enterprise training cache**: Coming soon. Enterprise is extending the same storage-aware caching model to training and feature engineering pipelines so large jobs can use GPU capacity more efficiently. ### 2. Feature engineering with Geneva For many teams, retrieval is only part of the problem. They also need a reliable way to derive new columns, run backfills, and keep feature pipelines close to the data they already store in LanceDB. This is what [Geneva](/geneva/) enables. * **Derived features**: Create new columns from existing data and user-defined logic without standing up a separate feature platform first. * **Large-table backfills**: Update or recompute features across large datasets without wiring your own distributed batch system around OSS tables. * **Shared workflows**: Use Geneva clusters, manifests, and jobs to manage feature engineering work in one place. ### 3. Enterprise-grade monitoring Production retrieval systems need more than search or training performance. Teams also need to observe the system, choose how it is deployed, and satisfy security and compliance requirements. * **Metrics and traces**: Integrates with existing observability systems for monitoring and distributed tracing using OpenTelemetry. * **Deployment choice**: Run LanceDB as a managed deployment or install it inside your own cloud account with [BYOC](/enterprise/deployment/). * **Private networking and compliance**: Designed for production environments that need encryption at rest, private connectivity options, and compliance coverage such as SOC 2 Type II and HIPAA. ## How is LanceDB Enterprise different from OSS? LanceDB OSS runs inside your application process. LanceDB Enterprise runs as a distributed cluster across many machines. Both are built on the same Lance columnar file format, so moving data from one edition to the other does not require a data conversion step. | Dimension | LanceDB OSS | LanceDB Enterprise | What the difference means | | :------------------------------ | :------------------------------------ | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------- | | **Mode** | Single process | Distributed fleet | OSS lives on one host. Enterprise spreads work across nodes and keeps serving even if one node fails. | | **Latency from object storage** | 500–1000 ms | 50–200 ms | Enterprise mitigates network delay with an SSD cache and parallel reads. | | **Throughput** | 10–50 QPS | Up to 10,000 QPS | A cluster can serve thousands of concurrent users; a single process cannot. | | **Cache** | None | Distributed NVMe cache | Enterprise keeps hot data near compute and avoids repeated S3 calls. | | **Indexing & compaction** | Manual | Platform-managed workflows | OSS requires operator-managed maintenance; Enterprise is moving more of that work into the platform as support expands. | | **Data format** | Supports multiple available standards | Supports multiple available standards | No vendor lock-in; data moves freely between editions. | | **Deployment** | Embedded in your code | BYOC or Managed | Enterprise meets uptime, compliance, and support goals that OSS cannot. | ### Architecture and scale LanceDB OSS is directly embedded into your service. The process owns all CPU, memory, and storage, so scale is limited to what one host can provide. LanceDB Enterprise separates routing, query execution, and background work across a cluster. You can add capacity by adding nodes, and the platform can keep serving traffic even when individual nodes are unhealthy. Read More: [LanceDB Enterprise Architecture](/enterprise/architecture/) ### Latency of data retrieval With LanceDB OSS, read latency depends heavily on where the data lives. If you use local disk or shared file storage, reads can be quite fast. But if you point an embedded deployment at S3, GCS, or Azure Blob, every read still takes a full round trip to remote object storage, especially when the data is cold. LanceDB Enterprise is designed for the object-storage-backed case. It uses NVMe SSDs as a hybrid cache and spreads reads across a distributed serving layer, so repeated reads can skip the full object-store round trip. The first read fills the cache, subsequent reads come from local disk, and parallel chunked reads further reduce tail latency. This matters when the application serves interactive dashboards, real-time recommendations, or other latency-sensitive workloads on top of object storage. Read More: [LanceDB Enterprise Benchmarks](/enterprise/benchmarks/) ### Throughput of search queries A single LanceDB OSS process shares one CPU pool with the rest of the application. When concurrent queries hit that CPU, retrieval and similarity processes compete for cores. The server cannot process more work in parallel and any extra traffic waits in the queue, raising latency without increasing queries per second. LanceDB Enterprise distributes queries across many execution nodes. A load balancer assigns queries to the least-loaded node, so throughput grows as more nodes join the cluster instead of stalling at a single-process ceiling. ### Caching of commonly retrieved data LanceDB OSS has no built-in cache. Every read repeats the same object-store round trip and pays the same latency penalty. LanceDB Enterprise shards a cache across the fleet with consistent hashing. Popular vectors remain on local NVMe drives until they age out under a least-recently-used policy. Cache misses fall back to the object store, fill the local shard, and serve future reads faster. This design slashes both latency and egress cost for workloads with temporal locality. ### Maintenance of vector indexes Vector indexes fragment when data is inserted, updated, or deleted. Fragmentation slows queries because the engine must scan more blocks. LanceDB OSS offers a CLI call to compact or rebuild the index, but you must schedule it yourself. LanceDB Enterprise is designed to move more of this maintenance into background platform workflows so operators spend less time managing it manually. We are continuing to expand distributed indexing and compaction support for the largest workloads. Read More: [Indexing in LanceDB](/indexing/) ### Deployment and governance When you work with LanceDB OSS, it is included as part of your binary, Docker, or serverless function. The footprint is small, and no extra services run beside it. LanceDB Enterprise comes in two flavors. The BYOC deployment installs the system inside your VPC, so data never leaves your account. The managed option hands day-to-day operations to the vendor, including patching, scaling, and ongoing monitoring. Both enterprise modes are designed for private networking, compliance, and operational oversight. Read More: [LanceDB Enterprise Deployment](/enterprise/deployment/) ## Usage differences between Enterprise and OSS The [quickstart](/quickstart) guide shows both local embedded connections and Enterprise `db://...` connections. Once connected to LanceDB, the table API is largely the same: create a table, search, filter, evolve the schema, and store multimodal records. However, there are some semantic differences worth understanding when your code is talking to LanceDB Enterprise. ### 1. Connection model In LanceDB Enterprise, your app connects via a `db://...` URI and sends requests to the cluster API. The cluster executes table operations on your behalf. Your code is coupled to a **managed service endpoint**, whereas embedded LanceDB is directly coupled to local or object-storage paths. ### 2. Returned table type Connecting to an Enterprise table via `open_table(...)` returns a `RemoteTable`, unlike embedded LanceDB, which returns a `LanceTable`. `RemoteTable` is a catalog-backed table accessed through a server/cluster, and does not support all the same methods as `LanceTable` (see below). ### 3. Materialization APIs For Python users working with LanceDB Enterprise, `RemoteTable` does not support table-level materialization methods like `table.to_arrow()` or `table.to_pandas()`. This protects users from accidentally materializing tables that are too large to fit in memory. Instead, materialize results through query/search builders, for example `table.search(...).limit(...).to_pandas()` or `table.query(...).to_arrow()`. For quick previews, use `table.head()`. ### 4. Maintenance lifecycle In Enterprise, maintenance operations like `optimize` and `compact_files` are handled by the cluster as background work. You can trigger them manually, but they are not required for performance or correctness in the same way they are in embedded LanceDB. That means maintenance is managed by platform behavior and cluster configuration, not by explicit per-table maintenance calls in your application code. ### 5. Guardrails and limits Enterprise can enforce platform-level guardrails, such as index/table limits and safety checks around operations like `merge_insert` when too many rows are unindexed. Embedded LanceDB mostly exposes storage/format-level behavior, and you tune many lifecycle tasks yourself. This means an operation in LanceDB Enterprise can fail due to service-level policy, not just because of local table shape or schema mismatch. ### 6. Cluster-managed background work In Enterprise, async writes and reindexing workflows are handled by cluster background systems. In embedded LanceDB, if you want ongoing upkeep, you usually schedule and run it yourself in your application or jobs. In practice, your app issues table operations, and the platform handles distributed orchestration for maintenance and indexing in the background. As a rule of thumb, all you need to remember is this: treat `db://...` as a remote service boundary, use query builders to fetch results, and otherwise interact with your tables as you would in embedded LanceDB. ## Which one should I use? [It's very simple to get started with OSS](/quickstart/): Get started with `pip install lancedb` and begin ingesting your data and vectors into LanceDB. LanceDB OSS makes sense when your dataset fits on one machine, traffic is still modest, and your team is comfortable handling maintenance tasks such as compaction or reindexing itself. Move to LanceDB Enterprise when retrieval becomes shared infrastructure for your business: your data or traffic has outgrown a single machine, you need private deployment options, or you want platform support for monitoring, security, and large-scale feature workflows. If these sound like your use cases, [reach out to us](mailto:contact@lancedb.com) and we can help you scope your workload and arrange an Enterprise proof of concept. # Security & Compliance Source: https://docs.lancedb.com/enterprise/security Learn about LanceDB Enterprise security features and best practices. LanceDB Enterprise maintains high security standards with SOC 2 Type II, HIPAA, and GDPR compliance. Our security framework is designed to provide enterprise-grade protection for your data and workloads across deployment models. ## Security Certifications * **SOC 2 Type II**: Independent audit confirming our security controls and operational effectiveness * **HIPAA Compliance**: Certified to handle protected health information (PHI) in healthcare applications * **GDPR Compliance**: Supports organizations with data privacy requirements under the General Data Protection Regulation * **Regular Audits**: Ongoing security assessments to maintain compliance standards ### Ongoing Compliance LanceDB maintains SOC 2 Type II, HIPAA, and GDPR compliance through ongoing audits and continuous improvement of our security practices as standards and risks evolve. Visit the [LanceDB Trust Center](https://trust.lancedb.com/) to learn more about LanceDB's security posture, data privacy practices, and to request access to security documentation. ## LanceDB Enterprise ### Data Security Customer data is strictly protected and remains within the confines of your account. We maintain rigorous data isolation and encryption protocols to ensure confidentiality. LanceDB Enterprise only receives telemetry data for monitoring system health. At LanceDB, customer data security is paramount. ### Encryption LanceDB Enterprise safeguards your data through encryption at rest, preventing unauthorized access. This comprehensive encryption covers all data stored within the object store and cache. # LanceDB Enterprise FAQ Source: https://docs.lancedb.com/faq/faq-enterprise Commonly asked questions about LanceDB Enterprise. This section provides answers to the most common questions asked about LanceDB Enterprise. For assistance with LanceDB Enterprise, please [contact us](mailto:support@lancedb.com) via email and one of our support staff will get back to you. ### Architecture and Fault Tolerance #### What's the impact of losing each component (query node, indexer, etc.) in the LanceDB stack? LanceDB Enterprise employs component-level replication to ensure fault tolerance and continuous operations. While the system remains fully functional during replica failures, transient performance impacts (e.g., elevated latency or reduced throughput) may occur until automated recovery completes.\ For architectural deep dives, including redundancy configurations, please contact the LanceDB team. #### What does plan executor cache versus not cache? The plan executor caches the table data, not the table indices. #### Should I use disk cache or memory cache for the plan executor? LanceDB implements highly performant consistent hashing for our plan executors. NVMe SSD caching is enabled by default for all deployments. #### How is the PE (Plan Executor) fleet shared? What fault tolerance exists (how many nodes can be lost)? LanceDB's plan executor is typically deployed with 2+ replicas for fault tolerance: * Mirrored Caches: Each query replica maintains synchronized copies of data subsets, enabling low-latency query execution. * Load Balancing: Traffic is distributed evenly across replicas. With a single replica failure, there is no downtime - the system remains operational with degraded performance, as the remaining replicas will handle all the traffic until the failed replica comes back online. ### Consistency #### How is strong/weak consistency configured in the enterprise stack? By default, LanceDB Enterprise operates in strong consistency mode. Once a write is successfully acknowledged, a new Lance dataset version manifest file is created. Subsequent reads always load the latest manifest file to ensure the most up-to-date data. However, this increases query latency and can place significant load on the storage system under high concurrency. We offer the `weak_read_consistency_interval_seconds` parameter to adjust consistency level (whose default value is zero). This parameter Defines the interval (in seconds) at which the system checks for table updates from other processes. **Recommended Setting** To balance consistency and performance, setting `weak_read_consistency_interval_seconds` to 30–60 seconds is often a good trade-off. This reduces unnecessary cloud storage operations while still keeping data reasonably fresh for most applications. Note that **this setting only affects read operations**. Write operations always remain strongly consistent. ### Indexing #### Can I use GPU for indexing? Yes! Please [contact](mailto:support@lancedb.com) the LanceDB team to enable GPU-based indexing for your deployment. Then you just need to call `create_index`, and the backend will use GPU for indexing. LanceDB is able to index a few billion vectors under 4 hours. ### Cluster Configuration #### What are the parameters that can be configured for my LanceDB cluster? LanceDB Enterprise offers granular control over performance, resilience, and operational behavior through a comprehensive set of parameters: replication factors for each component, consistency level, graceful shutdown time intervals, etc. Please contact the LanceDB team for detailed documentation on such parameter configurations. ### Monitoring and Alerts #### What are the metrics that LanceDB exposes for monitoring? We have various metrics set up for monitoring each component in the LanceDB stack: * Query node: RPS, query latency, error codes, slow take count, CPU/memory utilization, etc. * Plan executor: SSD cache hit/miss, CPU/memory utilization, etc. Please contact the LanceDB team for the comprehensive list of monitoring metrics. #### How do I integrate LanceDB's monitoring metrics with my monitoring dashboard? LanceDB uses Prometheus for metrics collection and OpenTelemetry (OTel) to export such metrics with data enrichment. The LanceDB team will work with you to integrate the monitoring metrics with your preferred dashboard. # LanceDB: Frequently Asked Questions Source: https://docs.lancedb.com/faq/faq-oss Commonly asked questions about LanceDB OSS. This section covers some common questions and issues that you may encounter when using LanceDB. ### Is LanceDB open source? LanceDB OSS is a permissively licensed embedded retrieval library available under an Apache 2.0 license. We also have a LanceDB Enterprise, a commercial product that can be deployed on a private cloud or a bring-your-own-cloud (BYOC) solution. LanceDB Enterprise transforms your data lake into a high-performance multimodal lakehouse. ### What is the difference between Lance and LanceDB? [Lance](https://github.com/lancedb/lance) is a modern lakehouse format for multimodal AI. It's perfect for building search engines, feature stores and being the foundation of large-scale ML training jobs requiring high performance IO and shuffles. It also has native support for storing, querying, and inspecting deeply nested data for robotics or large blobs like images, point clouds, and more. LanceDB is the multimodal lakehouse that's built on top of Lance, and utilizes the underlying optimized storage format to build efficient disk-based indexes that power semantic search & retrieval applications, from RAGs to QA bots to recommender systems. ### Why invent another data format instead of using Parquet? As we mention in our talk titled "[Lance, a modern columnar data format](https://www.youtube.com/watch?v=ixpbVyrsuL8)", Parquet and other tabular formats that derive from it are rather dated (Parquet is over 10 years old), especially when it comes to random access on vectors. We needed a format that's able to handle the complex trade-offs involved in shuffling, scanning, OLAP and filtering large datasets involving vectors, and our extensive experiments with Parquet didn't yield sufficient levels of performance for modern ML. [Our benchmarks](https://lancedb.com/blog/benchmarking-random-access-in-lance/) show that Lance is up to 1000x faster than Parquet for random access, which we believe justifies our decision to create a new data format for AI. ### Why build in Rust? We believe that the Rust ecosystem has attained mainstream maturity and that Rust will form the underpinnings of large parts of the data and ML landscape in a few years. Performance, latency and reliability are paramount to a vector DB, and building in Rust allows us to iterate and release updates more rapidly due to Rust's safety guarantees. Both Lance (the data format) and LanceDB (the database) are written entirely in Rust. We also provide Python, JavaScript, and Rust client libraries to interact with the database. ### What makes LanceDB different? LanceDB is among the few embedded vector DBs out there that we believe can unlock a whole new class of LLM-powered applications in the browser or via edge functions. Lance's multimodal nature allows you to store the raw data, metadata and the embeddings all at once, unlike other solutions that typically store just the embeddings and metadata. The Lance data format that powers our storage system also provides true zero-copy access and seamless interoperability with numerous other data formats (like Pandas, Polars, Pydantic) via Apache Arrow, as well as automatic data versioning and data management without needing extra infrastructure. ### How large of a dataset can LanceDB handle? LanceDB and its underlying data format, Lance, are built to scale to really large amounts of data. LanceDB OSS can comfortably handle millions of vectors on a single node, making it a great fit for most applications. Its disk-based indexes keep performance strong without requiring expensive infrastructure. If you need to scale to hundreds of millions of vectors or work with terabytes of data, we recommend [LanceDB Enterprise](/enterprise). Enterprise customers regularly operate on billions of rows, backed by distributed infrastructure designed for large-scale production workloads. ### Do I need to build a vector index to run vector search? No. LanceDB is blazing fast (due to its disk-based index) for even brute force kNN search, within reason. In our benchmarks, computing 100K pairs of 1000-dimension vectors takes less than 20ms. For small datasets of \~100K records or applications that can accept \~100ms latency, a vector index is usually not necessary. For large-scale (>1M) or higher dimension vectors, it is beneficial to create a vector index. See the [Vector Indexes](/indexing/vector-index/) section for more details. ### How can I speed up data inserts? LanceDB auto-parallelizes large writes when you call `table.add()` with materialized data such as `pa.Table`, `pd.DataFrame`, or `pa.dataset()`. No extra configuration is needed — writes are automatically split into partitions of \~1M rows or 2GB. For best results: * **Create an empty table first**, then call `table.add()`. The `add()` path enables automatic write parallelism, while passing data directly to `create_table()` does not. * **For file-based data**, use `pyarrow.dataset.dataset("path/to/data/", format="parquet")` so LanceDB can stream from disk without loading everything into memory. * **Avoid inserting one row at a time.** Each insert creates a new data fragment on disk. Batch your data into Arrow tables, DataFrames, or use iterators. See [Loading Large Datasets](/tables/create#loading-large-datasets) for full examples. ### Do I need to set a refine factor when using an index? Yes. LanceDB uses PQ, or Product Quantization, to compress vectors and speed up search when using an ANN index. However, because PQ is a lossy compression algorithm, it tends to reduce recall while also reducing the index size. To address this trade-off, we introduce a process called **refinement**. The normal process computes distances by operating on the compressed PQ vectors. The refinement factor (*rf*) is a multiplier that takes the top-k similar PQ vectors to a given query, fetches `rf * k` *full* vectors and computes the raw vector distances between them and the query vector, reordering the top-k results based on these scores instead. For example, if you're retrieving the top 10 results and set `refine_factor` to 25, LanceDB will fetch the 250 most similar vectors (according to PQ), compute the distances again based on the full vectors for those 250 and then re-rank based on their scores. This can significantly improve recall, with a small added latency cost (typically a few milliseconds), so it's recommended you set a `refine_factor` of anywhere between 5-50 and measure its impact on latency prior to deploying your solution. ### How can I improve IVF-PQ recall while keeping latency low? When using an IVF-PQ index, there's a trade-off between recall and latency at query time. You can improve recall by increasing the number of probes and the `refine_factor`. In our benchmark on the GIST-1M dataset, we show that it's possible to achieve >0.95 recall with a latency of under 10 ms on most systems, using \~50 probes and a `refine_factor` of 50. This is, of course, subject to the dataset at hand and a quick sensitivity study can be performed on your own data. You can find more details on the benchmark in a past [blog post](https://medium.com/etoai/benchmarking-lancedb-92b01032874a). ### How much data can LanceDB practically manage without affecting performance? We target good performance on \~10-50 billion rows and \~10-30 TB of data. For the best performance and scalability guarantees, check out [LanceDB Enterprise](/enterprise). ### Does LanceDB support concurrent operations? LanceDB can handle concurrent reads very well, and can scale horizontally. The main constraint is how well the storage layer you've chosen, scales. For writes, we support concurrent writing, though too many concurrent writers can lead to failing writes as there is a limited number of times a writer retries a commit. If you use Python's multiprocessing, you should probably not use `fork` as Lance is multi-threaded internally and `fork` and multi-threaded Python do not work well together. [Refer to this discussion](https://discuss.python.org/t/concerns-regarding-deprecation-of-fork-with-alive-threads/33555) for more information. # Frequently Asked Questions Source: https://docs.lancedb.com/faq/index Common questions about LanceDB Find answers to common questions about LanceDB across different deployment options and use cases. Reach out on [Discord](https://discord.gg/AUEWnJ7Txb) for community support or [contact us](mailto:support@lancedb.com) for Enterprise assistance. | Category | Description | | :---------------------------------------- | :---------------------------------------------------------------------------------- | | [LanceDB OSS](/faq/faq-oss) | Questions about LanceDB open source deployment, installation, and community support | | [LanceDB Enterprise](/faq/faq-enterprise) | Questions about LanceDB Enterprise features, security, compliance, and support | # Dependency Verification Source: https://docs.lancedb.com/geneva/deployment/dependency-verification Diagnose and resolve package version mismatches between local and distributed worker environments. When running Geneva UDFs on distributed workers, your code is serialized locally and executed on remote workers. If the worker environment differs from your local environment, you may encounter subtle and difficult-to-debug errors. ## Example environment mismatch errors | Symptom | Likely Cause | | ------------------------------------------------------------------ | ----------------------------------------- | | `TypeError: Enum.__new__() missing 1 required positional argument` | `attrs` version mismatch | | `TypeError: Can't instantiate abstract class` | Package structure differences | | `ArrowInvalid: cannot cast` / serialization errors | NumPy 1.x vs 2.x mismatch | | `ModuleNotFoundError` on workers | Package only installed locally | | Model loading failures | PyTorch version mismatch | | Permission denied errors | Missing API keys in envrionment variables | These issues are notoriously difficult to debug because the error messages often don't indicate the root cause. ## The `compare_ray_environments` Tool Geneva provides a diagnostic tool to compare your local environment against Ray workers. If you are encountering a hang or exception you can use the following diagnosis worklflow to resolve the problem. **Run the diagnostic tool** programatically or via the CLI. **Check PACKAGES and ENV VARS output sections for mismatches**. **Identify critical packages**: numpy, torch, pyarrow, attrs, pydantic. **Identify inconsistent environment variables**: `AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS` **Fix with manifest** for quick testing: **OPTIONAL: Build custom image** for production (if using KubeRay). ### Programmatic Usage ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.runners.ray.compare_env import compare_ray_environments # Compare and print (requires Geneva context to be initialize via `with db.context(..)`) result = compare_ray_environments() # Compare environments, filtering environment variables with specified prefix result = compare_ray_environments(env_prefix="PY") ``` ### CLI Usage ```bash CLI icon="terminal" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Connect to existing Ray cluster python -m geneva.runners.ray.compare_env # Start new local Ray cluster python -m geneva.runners.ray.compare_env --address local # Filter env vars by prefix python -m geneva.runners.ray.compare_env --env-prefix RAY # Show full JSON snapshots python -m geneva.runners.ray.compare_env --show-all # Skip sys.path comparison python -m geneva.runners.ray.compare_env --no-sys-path ``` ## Understanding the Output The tool outputs several sections to help you identify mismatches. ### PYTHON / PLATFORM Shows Python version and OS information for both environments: ``` === PYTHON / PLATFORM === Local: Python: 3.11.9 (main, Apr 19 2024, 16:48:06) [GCC 11.2.0] Impl : CPython Exec : /home/user/.venv/bin/python OS : Linux 5.15.0-generic (x86_64) Remote: Python: 3.11.9 (main, Apr 19 2024, 16:48:06) [GCC 11.2.0] Impl : CPython Exec : /home/ray/anaconda3/bin/python OS : Linux 5.4.0-aws (x86_64) ``` Watch for different Python versions or different OS types (macOS local vs Linux remote). #### Architecture Mismatch (macOS to Linux) If you see different OS types (e.g., `Darwin` locally vs `Linux` remotely), compiled extensions may fail with `ModuleNotFoundError` or segfaults. **Solution**: Run Geneva from the same OS/architecture as your cluster (typically Linux x86\_64). Use a Linux VM, container, or remote development environment. ### Environment Variables Environment variables present in only one environment: ``` === ENV VARS: keys only in LOCAL === + CONDA_PREFIX + VIRTUAL_ENV === ENV VARS: keys only in REMOTE === + RAY_ADDRESS + KUBERNETES_SERVICE_HOST ``` Missing `AWS_*` or `GOOGLE_APPLICATION_CREDENTIALS` can cause storage authentication failures. #### Passing Environment Variables to Workers If critical environment variables are missing on workers, you can pass them via the manifest or cluster configuration. **Option 1: Via Manifest** ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.manifest.builder import PipManifestBuilder import os manifest = ( PipManifestBuilder.create("my-manifest") .env_vars({ "AWS_ACCESS_KEY_ID": os.environ["AWS_ACCESS_KEY_ID"], "AWS_SECRET_ACCESS_KEY": os.environ["AWS_SECRET_ACCESS_KEY"], "MY_API_KEY": os.environ["MY_API_KEY"], }) .build() ) ``` **Option 2: Via Cluster Configuration** Avoid hardcoding secrets. Use `os.environ` to pass values from your local environment, or use a secrets manager in production. ### Packages The tool shows version mismatches and packages only present in one environment: ``` === PACKAGES: version mismatches === * numpy: local=1.26.4 remote=2.2.6 * torch: local=2.0.1 remote=2.8.0+cpu * attrs: local=23.2.0 remote=24.2.0 * pyarrow: local=14.0.1 remote=17.0.0 === PACKAGES: only in LOCAL === + my-custom-package + dev-tools === PACKAGES: only in REMOTE === + kuberay-client ``` Watch for major version differences (NumPy 1.x vs 2.x) and PyTorch version mismatches. #### Common Package Issues | Issue | Symptoms | Fix | | -------------------- | -------------------------------------------------------------------- | ------------------------------- | | **NumPy 1.x vs 2.x** | `ArrowInvalid`, `ValueError: cannot convert`, serialization failures | Pin `numpy==1.26.4` | | **PyTorch mismatch** | Model loading failures, CUDA errors, unexpected inference results | Pin to matching `torch` version | | **attrs mismatch** | `TypeError: Enum.__new__() missing 1 required positional argument` | Pin `attrs` to local version | | **Missing package** | `ModuleNotFoundError: No module named 'xyz'` | Add package to manifest | #### Fixing Package Mismatches **Option 1: Manifest pip Dependencies** Specify packages in a Geneva manifest for a quick fix: *Pros*: Quick, reusable across sessions, stored with your database. *Cons*: Slower startup (downloads packages), may not work for complex dependencies. **Option 2: Custom Ray Worker Image** For KubeRay deployments, build a custom worker image: ```dockerfile Dockerfile icon="docker" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Dockerfile.ray-worker FROM rayproject/ray:2.30.0-py311 # Install exact versions RUN pip install \ numpy==1.26.4 \ torch==2.0.1 \ attrs==23.2.0 \ geneva==0.8.0 # Copy any custom packages COPY ./my_udfs /app/my_udfs ``` Then reference in RayCluster spec: ```yaml Kubernetes icon="kubernetes" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} spec: workerGroupSpecs: - template: spec: containers: - image: your-registry/ray-worker:latest ``` *Pros*: Fastest startup, reproducible. *Cons*: Requires image build/push workflow. **Option 3: Conda Environment** Use a conda environment on workers via the cluster builder: Or specify conda channels and dependencies inline: *Pros*: Best for complex dependencies with native libraries (ffmpeg, CUDA). *Cons*: Slower environment creation, requires conda on workers. ## API Reference For detailed API documentation on the environment comparison functions, see the [Geneva Diagnostics API Reference](https://lancedb.github.io/geneva/api/diagnostics). # Deploy Geneva using Helm Source: https://docs.lancedb.com/geneva/deployment/helm Learn how to deploy Geneva on Kubernetes using the Geneva Helm Chart **Feature Engineering is deployed automatically in LanceDB Enterprise** In self-managed environments, Geneva can be installed into existing Kubernetes clusters using Helm. Please [contact LanceDB](https://lancedb.com/contact/) for access to the Helm Chart and related resources. ## Pre-requisites * An existing Kubernetes cluster * An existing node pool(s) for Geneva workloads. By default, Geneva uses node selector `{"geneva.lancedb.com/ray-head": "true"}` for Ray head nodes, and `{"geneva.lancedb.com/ray-worker-cpu": "true"}` and `{"geneva.lancedb.com/ray-worker-gpu": "true"}` for Ray CPU worker and Ray GPU worker nodes respectively. This can be overridden in the Geneva client. * Geneva Helm chart. Please [contact LanceDB](https://lancedb.com/contact/) for access to the Helm Chart and related resources. For more information on deploying the required cloud resources, see the [manual deployment instructions](/geneva/deployment/). ## Geneva Helm Chart The Helm chart includes resources required for running [Geneva](https://lancedb.com/docs/geneva/) in Kubernetes. It includes services, service accounts, RBAC roles, etc. that are used by the Geneva client to manage resources. ## Install 1. Authenticate with Kubernetes cluster, i.e. update kubeconfig 2. Configure Helm chart values In values.yaml, configure the service account, node selectors, and cloud resources, if applicable. ``` geneva: # Object storage root URI rootUri: value: "s3://my-data-bucket" serviceAccount: # Service account for Geneva worker pods and services annotations: # Set per-CSP annotations to provide access to CSP resources, i.e. # eks.amazonaws.com/role-arn: arn:aws:iam::0123456789:role/geneva_service_role # iam.gke.io/gcp-service-account: geneva-service-account@my-project.iam.gserviceaccount.com gcp: # GCP service account email for the Geneva client. # It should have access to the GKS cluster and "roles/storage.objectUser" # permissions on the object storage bucket. # e.g., geneva-client-sa@project-id.iam.gserviceaccount.com clientServiceAccount: "" aws: # AWS IAM role ARN to be assumed by the Geneva client. # This role should have an access entry to the cluster with username matching the role ARN. # It should also have r/w access to the object storage bucket. # e.g., arn:aws:iam::123456789012:role/geneva-client-role clientRoleArn: "" azure: # Azure managed identity client ID for the Geneva client. # This identity should have a federated credential for the LanceDB namespace # and Storage Blob Data Contributor role on the storage account. clientPrincipalId: "" ``` 3. Install kuberay operator ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} export NAMESPACE=lancedb helm repo add kuberay https://ray-project.github.io/kuberay-helm/ helm repo update helm install kuberay-operator kuberay/kuberay-operator -n $NAMESPACE --create-namespace ``` 4. Install NVIDIA device plugin (if using GPU nodes) For GPU support, the NVIDIA device plugin must be installed in your EKS cluster: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.0/deployments/static/nvidia-device-plugin.yml > nvidia-device-plugin.yml kubectl apply -f nvidia-device-plugin.yml ``` 5. Install Geneva Helm chart ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} helm install geneva ./geneva -n $NAMESPACE --create-namespace ``` ## Default cluster and manifest In LanceDB Enterprise, backfill and refresh jobs run on a **default cluster** (the compute pool jobs run on) and a **default manifest** (the Python dependency environment — image and packages). Configuring these in the LanceDB Enterprise chart lets jobs run out of the box without per-job configuration. They are set under `geneva.defaults` in the chart's `values.yaml`: ```yaml theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} geneva: defaults: cluster: cluster_type: external_ray name: deployment-default ray_address: "ray://raycluster-kuberay-head-svc.lancedb.svc.cluster.local:10001" manifest: name: deployment-default pip: [geneva, pyarrow, lancedb, pylance] head_image: rayproject/ray:2.54.0-py312 worker_image: rayproject/ray:2.54.0-py312 skip_site_packages: true ``` If no default is configured, jobs must specify a manifest explicitly. Individual transforms can override the default manifest by pinning one with `@udf` / `@chunker` / `@udtf` (see [Advanced Job Configuration](/geneva/jobs/advanced-job-configuration)); to override the cluster at runtime, use an [Advanced Execution Context](/geneva/jobs/contexts). ## Providing a Ray cluster The LanceDB Helm chart can be configured to deploy a static KubeRay cluster, provision KubeRay clusters on demand per job, or use an existing Ray cluster. ### Use default LanceDB Enterprise Ray cluster (default) By default, LanceDB Enterprise will use a shared, statically provisioned Ray cluster for job execution. This can be enabled in the Helm chart by setting the following values. ```yaml theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} raycluster: enabled: true global: rayclusterUri: "ray://raycluster-kuberay-head-svc.lancedb.svc.cluster.local:10001" ``` Configuration for the Ray cluster can be specified by modifying raycluster.yaml Helm values. ### Provision KubeRay clusters on demand Set `global.rayclusterUri` to an empty value to provision ephemeral KubeRay clusters on-demand for each execution job. The default KubeRay cluster configuration is specified in `geneva.defaults.cluster`, i.e. ```yaml theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} geneva: defaults: cluster: cluster_type: kuberay name: deployment-default kuberay: namespace: lancedb config_method: IN_CLUSTER head_group: service_account: geneva-service-account num_cpus: 2 memory: 8Gi image: rayproject/ray:2.54.0-py312 worker_groups: - name: cpu service_account: geneva-service-account num_cpus: 4 memory: 8Gi replicas: 2 min_replicas: 0 max_replicas: 4 idle_timeout_seconds: 60 node_selector: geneva.lancedb.com/ray-worker-cpu: "true" image: rayproject/ray:2.54.0-py312 ``` ### Use an external Ray cluster Self-managed enterprise customers can bring an existing Ray cluster to run Geneva jobs. Simply set the rayclusterUri property in the Helm chart to a Ray address that can be accessed from the LanceDB Enterprise deployment. ```yaml theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} global: rayclusterUri: "ray://my-ray-cluster.my-ns.svc.cluster.local:10001" ``` # Manual Deployment on Kubernetes Source: https://docs.lancedb.com/geneva/deployment/index Learn how to deploy Geneva on Kubernetes using KubeRay for distributed feature engineering workflows on GKE and EKS. **Feature Engineering is deployed automatically in LanceDB Enterprise** For manual installation in self-managed environments, follow the instructions below. Feature Engineering can be deployed as part of LanceDB Enterprise in managed or self-managed environments. First class support is provided for Azure, AWS, and GCP, including deployment automation via Terraform and Helm. ## Prerequisites * Kubernetes cluster with KubeRay 1.1+ operator installed * Ray 2.43+ See below for manual installation instructions for: * Amazon Web Services (AWS) Elastic Kubernetes Service (EKS) * Google Cloud Platform (GCP) Google Kubernetes Engine (GKE) ## Basic Kubernetes Setup Kubernetes resources can be deployed automatically via [Helm](/geneva/deployment/helm/) or manually via the instructions below. In the following sections we'll use these variables: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} NAMESPACE=lancedb # replace with your actual namespace if different KSA_NAME=geneva-ray-runner # replace with an identity name ``` ### Kubernetes Service Account (KSA) Inside your Kubernetes cluster, you need a Kubernetes service account which provides the credentials your k8s pods (Ray) run with. Here's how to create your KSA. #### Create a Kubernetes Service Account (KSA) ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl create namespace $NAMESPACE # skip if it already exists kubectl create serviceaccount $KSA_NAME \ --namespace $NAMESPACE ``` You can verify using: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl get serviceaccounts -n $NAMESPACE $KSA_NAME ``` The Kubernetes service account (KSA) needs RBAC permissions inside the k8s cluster to provision Ray clusters via CRDs. #### Create a k8s Role Create a k8s role that can access the Ray CRD operations. ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl apply -f - < #### Geneva Security Requirements In the following sections we'll use these variables: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} NAMESPACE=lancedb # replace with your actual namespace if different KSA_NAME=geneva-ray-runner # replace with an identity name PROJECT_ID=... # replace with your google cloud project name GSA_EMAIL=${KSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com LANCEDB_URI=gs://bucket/db # replace with your own path ``` #### Google Service Account (GSA) To give your k8s workers the ability to read and write from your LanceDB buckets, your KSA needs to be bound to a Google Cloud service account (GSA) with those grants. With this setup, any pod using the KSA will automatically get a token that lets it impersonate the GSA. Let's set this up: **Create a Google Cloud Service Account** ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} gcloud iam service-accounts create ${KSA_NAME} \ --project=${PROJECT_ID} \ --description="Service account for ray workloads in GKE" \ --display-name="Ray Runner GSA" ``` You can verify this using: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} gcloud iam service-accounts list --filter="displayName:Ray Runner GSA" ``` > **Warning**: You need `roles/iam.serviceAccountAdmin` or minimally `roles/iam.serviceAccountTokenCreator` rights to run these commands. Next, you'll need to verify that your KSA is bound to your GSA and has `roles/iam.workloadIdentityUser`: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} gcloud iam service-accounts get-iam-policy $GSA_EMAIL \ --project=$PROJECT_ID \ --format="json" | jq '.bindings[] | select(.role=="roles/iam.workloadIdentityUser")' ``` Give your GSA rights to access the LanceDB bucket: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} gcloud storage buckets add-iam-policy-binding ${LANCEDB_URI} \ --member="serviceAccount:${KSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ --role="roles/storage.objectAdmin" ``` #### GKE Workload Identity A GKE workload identity is required to enable k8s workloads access Google Cloud services securely and without needing to manually manage service account keys. The workload identity is attached to Google Cloud service accounts (GSA) and mapped to a Kubernetes service account (KSA). This feature needs to be enabled on the GKE cluster. You can confirm that your workers have abilities to read/write to the LanceDB bucket: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl run gcs-test --rm -it --image=google/cloud-sdk:slim \ --serviceaccount=${KSA_NAME} \ -n ${NAMESPACE} \ -- bash echo "hello" > test.txt gsutil cp test.txt ${LANCEDB_URI}/demo-check/test-write.txt ``` Confirm the identity inside the pod: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl -H "Metadata-Flavor: Google" \ http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email ``` ## Geneva on AWS EKS Geneva can be used to provision Ray clusters running in Amazon Web Services (AWS) Elastic Kubernetes Service (EKS). In the following sections we'll use these variables: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} NAMESPACE=lancedb # replace with your actual namespace if different CLUSTER=geneva # replace with your actual namespace if different KSA_NAME=geneva-ray-runner # replace with an identity name ``` ### EKS Node Groups EKS allows you to specify templates for virtual machines in "node groups". These allow you to manage and configure resources such as the number of CPUs, number of GPUs, amount of memory, and if instances are spot or regular virtual machines. You can define your node groups however you want but Geneva uses three specific Kubernetes labels when deploying Ray pods on EKS: `ray-head`, `ray-worker-cpu`, `ray-worker-gpu` * **Head nodes** are where the Ray dashboard and scheduler run. They should be non-spot instances and should not have processing workloads scheduled on them. Geneva looks for nodes with the `geneva.lancedb.com/ray-head: true` k8s label for this role. * **CPU Worker nodes** are where distributed processing that does not require GPU should be scheduled. Geneva looks for nodes with the `geneva.lancedb.com/ray-worker-cpu: true` k8s label when these nodes are requested. * **GPU Worker nodes** are where distributed processing that require GPU should be scheduled. Geneva looks for nodes with the `geneva.lancedb.com/ray-worker-gpu: true` k8s label when these nodes are requested. ### Install KubeRay Operator Using Helm Geneva requires the KubeRay operator to be installed in your EKS cluster. ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} helm repo add kuberay https://ray-project.github.io/kuberay-helm/ helm repo update helm install kuberay-operator kuberay/kuberay-operator -n $NAMESPACE ``` ### Install NVIDIA Device Plugin For GPU support, the NVIDIA device plugin must be installed in your EKS cluster: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} curl https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.0/deployments/static/nvidia-device-plugin.yml > nvidia-device-plugin.yml kubectl apply -f nvidia-device-plugin.yml ``` ### Configure Access Control eks-auth #### Environment IAM Principal Geneva must be run in an environment with access to [AWS credentials](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html) with permissions to `sts:AssumeRole` on the Geneva Client IAM Role. For example, this could be a laptop with credentials provided by environment variables, or an EC2 instance with credentials provided via Instance Profile. #### Create IAM Role for Geneva Client The Geneva Client IAM Role is assumed by the Geneva client to provision the Kuberay cluster and run remote jobs. This role requires IAM permissions to access the storage bucket and Kubernetes API. Create an IAM role with the following policy: ```json theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} { "Version": "2012-10-17", "Statement": [ { "Sid": "ClusterAccess", "Action": [ "eks:DescribeCluster", "eks:AccessKubernetesApi" ], "Effect": "Allow", "Resource": "" }, { "Sid": "AllowListBucket", "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": "arn:aws:s3:::" }, { "Sid": "AllowAllS3ObjectActions", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:HeadObject" ], "Resource": "arn:aws:s3:::/*" } ] } ``` This role should also have a trust policy with `sts:AssumeRole` permissions for any principal initiating the Geneva client. When using Geneva, this role can be specified with the `role_name` RayCluster parameter. #### Create EKS Access Entry Create an [EKS access entry](https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html) to allow the Geneva Client Role to access the Kubernetes API for the EKS Cluster. ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} aws eks create-access-entry --cluster-name $CLUSTER --principal-arn --type STANDARD aws eks associate-access-policy --cluster-name $CLUSTER --principal-arn --access-scope type=cluster --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy ``` #### Create EKS OIDC Provider Create an OIDC provider for your EKS cluster. This is required to allow Kubernetes Service Accounts (KSA) to assume IAM roles. See [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html#_create_oidc_provider_console). #### Create IAM Role for Service Account An IAM role is required for the Kubernetes Service Account (KSA) that will be used by the Ray head and worker pods. This role must have permissions to access the storage bucket and to describe the EKS cluster: ```json theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} { "Version": "2012-10-17", "Statement": [ { "Sid": "ClusterAccess", "Action": [ "eks:DescribeCluster" ], "Effect": "Allow", "Resource": "" }, { "Sid": "AllowListBucket", "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": "arn:aws:s3:::" }, { "Sid": "AllowAllS3ObjectActions", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:HeadObject" ], "Resource": "arn:aws:s3:::/*" } ] } ``` In addition, it must have a trust policy allowing the EKS OIDC provider to assume the role from the Kubernetes Service Account: ```json theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { ":aud": "sts.amazonaws.com", ":sub": "system:serviceaccount:$NAMESPACE:$KSA_NAME" } } } ] } ``` #### Associate the IAM Role with the Kubernetes Service Account Modify the Kubernetes Service Account created in "Basic Kubernetes setup" to associate it with the IAM role created above. The role ARN is specified using `eks.amazonaws.com/role-arn` annotation: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl annotate serviceaccount "$KSA_NAME" \ -n "$NAMESPACE" \ "eks.amazonaws.com/role-arn=$ROLE_ARN" \ --overwrite ``` ### Initialize the Ray Cluster Initialize the Ray cluster using the node selectors and metadata from above: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.runners.ray._mgr import ray_cluster from geneva.runners.ray.raycluster import (K8sConfigMethod, _HeadGroupSpec, _WorkerGroupSpec) head_spec = _HeadGroupSpec( service_account="geneva-ray-runner", num_cpus=1, memory=2048, node_selector={"geneva.lancedb.com/ray-head": "true"}, ) worker_spec = _WorkerGroupSpec( name="worker", min_replicas=1, service_account="geneva-ray-runner", num_cpus=2, memory=4096, node_selector={"geneva.lancedb.com/ray-worker-cpu": "true"}, ) with ray_cluster( name="my-ray-cluster", namespace="lancedb", cluster_name="geneva", config_method=K8sConfigMethod.EKS_AUTH, region="us-east-1", use_portforwarding=True, head_group=head_spec, worker_groups=[worker_spec], role_name="geneva-client-role", ) as cluster: table.backfill("embedding") ``` # Troubleshooting Geneva Deployments Source: https://docs.lancedb.com/geneva/deployment/troubleshooting Learn how to diagnose and resolve common issues with Geneva deployments, including version compatibility, permissions, and serialization errors. We'll cover common problems you may encounter when using Geneva and troubleshooting tips to solve them. ## Common Issues to Verify Here are some areas to verify to identify the source of problems with your Geneva deployment: * **Versions compatibility** (Ray, Python, Lance) * **Remote Ray execution** and hardware resource availability * **Sufficient permissions** to access data * **Worker code** only returns serializable values (no open files, no GPU resident buffers) ## Confirming Dependency Versions Geneva uses Ray for distributed execution. Ray requires the version deployed cluster services and clients to be exactly the same. Minor versions of Python must match both on client and cluster services (e.g. 3.10.3 and 3.10.5 are ok, but 3.10.3 and 3.12.1 are not.) Geneva has been tested with Ray 2.44+ and Python 3.10.x and 3.12.x. You can run this code in your notebook to verify your environment matches your expectations: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} !python --version !pip show lancedb # need 0.22.0b0+ !echo $VIRTUAL_ENV ``` ## Confirming Remote Ray Execution Geneva allows you to specify the resources of your worker nodes. You can verify that your cluster has the resources (e.g. GPUs) available for your jobs and that remote execution is working properly. You can get some basic information about resources available to your Ray: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} print(ray.available_resources()) ``` You can verify basic remote execution via Ray: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @ray.remote def check_remote(): return "Hello from a worker" print(ray.get(check_remote.remote())) ``` You can also verify that versions of specific libraries are present in the execution environment: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # does ray have cuda? @ray.remote def check_pyarrow(): import pyarrow return pyarrow.__version__ print(ray.get(check_pyarrow.remote())) ``` > **Note**: You should execute Geneva code from a machine or VM that has the same architecture and OS type as the nodes in your cluster. This will allow for shared libraries to be shipped. For example, if you use a Mac to host a Jupyter notebook, Geneva will push Mac libraries to your Linux cluster and likely result in module not found errors due to OS/architecture differences. For GPU-dependent UDFs and jobs, you can verify that GPU worker nodes have the CUDA library: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # does ray have cuda? @ray.remote(num_gpus=1) def check_cuda(): import torch return torch.version.cuda, torch.cuda.is_available() print(ray.get(check_cuda.remote())) ``` ## Confirming Sufficient Permissions While your notebook or working environment may have credentials to read and write to particular buckets, your jobs need sufficient rights to read and write to them as well. Adding `import geneva` to any remote function can help verify that your workers have sufficient grants. Here we add `import geneva` to help trigger potential permissions problems: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @ray.remote(num_gpus=1) def check_cuda(): import geneva # this is currently required before other imports import torch return torch.version.cuda, torch.cuda.is_available() print(ray.get(check_cuda.remote())) ``` ### GCE Permissions Errors in Job Logs If you are using Geneva managed Ray deployed on GKE, the errors may look like this: ``` PermissionError: [Errno 13] google::cloud::Status(PERMISSION_DENIED: Permanent error, with a last message of Caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist). error_info={reason=forbidden, domain=global, metadata={gcloud-cpp.retry.function=GetObjectMetadata, gcloud-cpp.retry.reason=permanent-error, gcloud-cpp.retry.original-message=Caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist)., http_status_code=403}}). Detail: [errno 13] Permission denied ``` This indicates that your workers and/or head node are not being run with the correct service account or with an account that has sufficient access. Please double check the service account's accesses and make sure to add your service account that has access to the buckets as a parameter to your Head and Worker specs. See `service_account="geneva-integ-test"` below: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} raycluster = ray_cluster( name= k8s_name, namespace=k8s_namespace, use_portforwarding=True, head_group=_HeadGroupSpec( num_cpus=8, service_account="geneva-integ-test" ), worker_groups=[ _WorkerGroupSpec( name="cpu", num_cpus=60, memory="120G", service_account="geneva-integ-test", ), _WorkerGroupSpec( name="gpu", num_cpus=8, memory="32G", num_gpus=1, service_account="geneva-integ-test", ), ], ) ``` ## Serialization Errors Serialization is a critical subsystem of Geneva. In order to store UDFs and perform distributed execution, both code and data must be serializable. Errors in this area can be subtle and difficult to find. There are a few basic rules: 1. **Python objects** passed to distributed processes or written to LanceDB must be able to be pickled or unpickled using the Python pickle or cloudpickle library. 2. **Python code** used for distributed execution, including UDFs used to calculate values written to columns must be able to be pickled or unpickled using the Python pickle or cloudpickle library. 3. **Python code or objects** need to have the same encoding and representation on the client-side and the server-side. Below is a list of error categories and examples and how to fix them. ### Serialization Library Mismatches Any Python code and objects must be able to be serialized by the client and deserialized on the server side, and vice versa. This includes objects that are generated on the fly such as those using the `attrs` library. The distributed processing engine Geneva uses, Ray, also depends on the `attrs` library. Different versions may create different object signatures that are not compatible when shipped from client-side to server-side and vice versa. This means you'll need to have compatible versions of this library on both sides. Here's an example error message. It is subtle and does not directly point to the `attrs` library: ``` ... File "/home/runner/work/geneva/geneva/.venv/lib/python3.12/site-packages/ray/util/client/common.py", line 414, in _prepare_client_task self._ensure_ref() File "/home/runner/work/geneva/geneva/.venv/lib/python3.12/site-packages/ray/util/client/common.py", line 384, in _ensure_ref self._ref = ray.worker._put_pickled( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/runner/work/geneva/geneva/.venv/lib/python3.12/site-packages/ray/util/client/worker.py", line 509, in _put_pickled raise cloudpickle.loads(resp.error) TypeError: Enum.__new__() missing 1 required positional argument: 'value' ``` This was solved by updating the `attrs` module on the client side to use the same version found on the server side. ### Objects with Unserializable Elements Python objects may have internal references to unpickleable objects such as open file handles or open network clients with machine specific state. There are two strategies here: 1. **Remove the reference** to unpickleable objects. 2. **Keep objects with unserializable state** only on the client or only on the server. This could be moving clients into the UDF function, or converting objects into serializable versions before transmitting them. For example, creating clients or opening files must be inside the UDF. You may see pickling-related errors like this: ``` ... raise PicklingError( _pickle.PicklingError: Pickling client objects is explicitly not supported. Clients have non-trivial state that is local and unpickleable. ``` Geneva pulls in your UDFs so they can be sent to remote worker nodes. For the method to be sent, the data must be "pickleable". **So instead of this:** ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from google.cloud import storage storage_client = storage.Client() # this has unpickleable state bucket = storage_client.bucket(BUCKET_NAME) # this has a reference to storage_client ... @udf def udf_function(...) ... blob = bucket.blob(video_path) # the udf's closure captures the unpickleable storage_client ... ``` **Do this:** ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from google.cloud import storage # ... @udf def udf_function(...) # ... storage_client = storage.Client() # this has unpickleable state bucket = storage_client.bucket(BUCKET_NAME) blob = bucket.blob(video_path) # blob is bytes and is pickleable so is safe # ... ``` ### Disconnect or Serialization Errors with GPU Dependent UDFs When using GPU code, the typical process loads some values and tensors from CPU memory to GPU memory. Even after moving data (`data.cpu().tolist()`), there may be references to GPU memory. While this is not a problem with local execution, when doing a distributed job it may cause problems because the GPU references are not serializable and not needed. You must take steps to eliminate references to structures in GPU memory since they cannot be serialized and sent between workers. This can be achieved by explicitly disconnecting references to the GPU memory (`data.cpu().detach().tolist()`) to get only-CPU resident fully serializable objects. Here are some typical error messages: ``` Exception in thread Thread-27 (_proxy): Traceback (most recent call last): File "/home/jon/.pyenv/versions/3.10.16/lib/python3.10/threading.py", line 1016, in _bootstrap_inner self.run() File "/home/jon/proj/geneva-deepseek-vl2/.venv/lib/python3.10/site-packages/ipykernel/ipkernel.py", line 772, in run_closure _threading_Thread_run(self) File "/home/jon/.pyenv/versions/3.10.16/lib/python3.10/threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "/home/jon/proj/geneva-deepseek-vl2/src/geneva/runners/ray/_portforward.py", line 172, in _proxy {s1: s2, s2: s1}[s].sendall(data) BrokenPipeError: [Errno 32] Broken pipe Log channel is reconnecting. Logs produced while the connection was down can be found on the head node of the cluster in `ray_client_server_[port].out` 2025-04-11 02:25:21 INFO Starting proxy from pod to client 2025-04-11 02:25:21 INFO Proxy started 2025-04-11 02:25:21 INFO Proxying between and 2025-04-11 02:25:21 INFO Waiting for client connection 2025-04-11 02:25:21,828 ERROR dataclient.py:330 -- Unrecoverable error in data channel. --------------------------------------------------------------------------- ... File ~/proj/geneva-deepseek-vl2/.venv/lib/python3.10/site-packages/grpc/_channel.py:1006, in _end_unary_response_blocking(state, call, with_call, deadline) 1004 return state.response 1005 else: -> 1006 raise _InactiveRpcError(state) _InactiveRpcError: <_InactiveRpcError of RPC that terminated with: status = StatusCode.NOT_FOUND details = "Failed to serialize response!" debug_error_string = "UNKNOWN:Error received from peer {created_time:"2025-04-11T02:25:22.209209484+00:00", grpc_status:5, grpc_message:"Failed to serialize response!"}" > Unexpected exception: Traceback (most recent call last): File "/home/jon/proj/geneva-deepseek-vl2/.venv/lib/python3.10/site-packages/ray/util/client/logsclient.py", line 67, in _log_main for record in log_stream: File "/home/jon/proj/geneva-deepseek-vl2/.venv/lib/python3.10/site-packages/grpc/_channel.py", line 543, in __next__ return self._next() File "/home/jon/proj/geneva-deepseek-vl2/.venv/lib/python3.10/site-packages/grpc/_channel.py", line 969, in _next raise self grpc._channel._MultiThreadedRendezvous: <_MultiThreadedRendezvous of RPC that terminated with: status = StatusCode.NOT_FOUND details = "Logstream proxy failed to connect. Channel for client bd854100340640fb8b5770d2bf173197 not found." debug_error_string = "UNKNOWN:Error received from peer {grpc_message:"Logstream proxy failed to connect. Channel for client bd854100340640fb8b5770d2bf173197 not found.", grpc_status:5, created_time:"2025-04-11T02:25:32.223710374+00:00"}" > ``` # End-to-End Example Source: https://docs.lancedb.com/geneva/end-to-end A complete Geneva example on LanceDB Enterprise — create a table, backfill computed columns, and build a materialized view with embeddings. This example walks through a complete Geneva workflow on LanceDB Enterprise: creating a raw table, adding computed columns with a distributed backfill, and materializing a view with embeddings for downstream search. The dataset is a product catalog with titles and descriptions. We'll compute a `word_count` feature column, then create a materialized view that adds text embeddings. ## 0. What you need to run this All you need is an existing **LanceDB Enterprise deployment**. Distributed job execution, clusters, and dependency manifests are managed for you — there is no Kubernetes or cluster setup in this example. ## 1. Connect and create a table ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import os import pyarrow as pa import geneva # Connect to LanceDB Enterprise db = geneva.connect( uri="db://my-db", host_override=os.getenv("LANCEDB_URI"), api_key=os.getenv("LANCEDB_API_KEY"), ) # Create a raw product table schema = pa.schema([ pa.field("product_id", pa.int64()), pa.field("title", pa.string()), pa.field("description", pa.string()), pa.field("category", pa.string()), pa.field("price", pa.float64()), ]) data = pa.table({ "product_id": [1, 2, 3, 4, 5], "title": ["Chainmail Coif", "Jousting Lance Grip Tape", "Dragon-Repellent Spray", "Sword Squeegee", "Visor Windshield Wipers"], "description": [ "Premium riveted chainmail head covering. Breathable enough for dragon fire, probably.", "Non-slip grip tape for jousting lances. 3000 PSI tensile strength. Void where tilting is prohibited.", "All-natural herbal spray. Dragons hate it. Effectiveness not guaranteed against actual dragons.", "Ergonomic squeegee fits all standard broadswords. Removes blood, mud, and existential dread.", "Hand-cranked windshield wipers for full-face visors. Never ride blind into battle again.", ], "category": ["armor", "tournament", "defense", "maintenance", "armor"], "price": [129.99, 45.00, 59.99, 34.99, 24.99], }) try: db.drop_table("products_raw") except Exception: pass table = db.create_table("products_raw", data=data, schema=schema) ``` ## 2. Define UDFs ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa from geneva import udf @udf(data_type=pa.int32()) def word_count(description: str) -> int: """Count words in the description.""" return len(description.split()) @udf(data_type=pa.string()) def price_tier(price: float) -> str: """Bucket price into tiers.""" if price < 30: return "budget" elif price < 75: return "mid-range" else: return "premium" ``` ## 3. Register columns and run a backfill Register the UDFs as virtual columns and trigger a backfill. The job runs on your deployment's default distributed execution environment — no cluster or context to configure. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Add computed columns table.add_columns({ "word_count": word_count, "price_tier": price_tier, }) # Run the backfills table.backfill("word_count") table.backfill("price_tier") ``` ## 4. Create a materialized view with embeddings The embedding model needs extra Python dependencies (`sentence-transformers`, `torch`). Rather than configuring a deployment-wide environment, we bundle those dependencies **with the UDF** using `@udf(manifest=...)`. The manifest is snapshotted onto the view, so refreshes use it automatically. The materialized view selects a subset of columns from the source table — here we drop `price` and `price_tier`, keeping only what's needed for search — plus a derived `embedding` column. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva import udf from geneva.manifest import GenevaManifest # Bundle the embedding model's dependencies and attach them to the UDF embed_manifest = ( GenevaManifest.create_pip("embedding-deps") .pip(["sentence-transformers==3.3.1", "torch==2.5.1"]) .build() ) @udf(data_type=pa.list_(pa.float32(), 384), manifest=embed_manifest) class EmbedDescription: def __init__(self): self.model = None def __call__(self, description: str) -> list[float]: if self.model is None: from sentence_transformers import SentenceTransformer self.model = SentenceTransformer("BAAI/bge-small-en-v1.5") return self.model.encode(description, normalize_embeddings=True).tolist() # Build a query that selects search columns plus the derived embedding query = table.search(None).select({ "product_id": "product_id", "title": "title", "description": "description", "category": "category", "word_count": "word_count", "embedding": EmbedDescription(), }) # Create the materialized view and populate it — the embedding UDF runs on refresh db.create_materialized_view("products_enriched", query) enriched = db.open_table("products_enriched") enriched.refresh() ``` ## 5. Query the enriched table ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} enriched = db.open_table("products_enriched") # Vector search results = ( enriched.search([0.1] * 384, vector_column_name="embedding") .limit(3) .to_arrow() ) # Filtered search — armor category only armor_results = ( enriched.search([0.1] * 384, vector_column_name="embedding") .where("category = 'armor'") .limit(3) .to_arrow() ) ``` ## 6. Incremental refresh As new products are added to the source table, backfill the new rows and refresh the view to compute embeddings for them — only the new rows are processed: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Append new products new_data = pa.table({ "product_id": [6, 7], "title": ["Moat Floaties", "Knight-Night Sleep Mask"], "description": [ "Inflatable arm floaties in battleship grey. Because even knights can't always swim in full plate.", "Padded silk sleep mask embroidered with your coat of arms. Blocks out 100% of torchlight.", ], "category": ["defense", "armor"], "price": [29.99, 19.99], }) table.add(new_data) # Only null/new values are computed table.backfill("word_count") table.backfill("price_tier") # Incrementally materialize the new rows (including their embeddings) into the view enriched.refresh() ``` To keep source columns in sync automatically, mark their UDFs with `@udf(auto_backfill=True)`. See [Backfilling](/geneva/jobs/backfilling/). # Getting Started Source: https://docs.lancedb.com/geneva/getting-started Connect to LanceDB Enterprise, define a UDF, and run a distributed backfill — from a notebook or a script. Connect to your LanceDB Enterprise deployment, define a UDF, and run a distributed backfill — all from a notebook or a script. No cluster setup required. ## Installation Geneva is published on [PyPI](https://pypi.org/project/geneva/). Install the latest stable release with [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip`. Newer pre-release builds with the latest features are also available on LanceDB's Fury indexes — see [Pre-release builds](#pre-release-builds) below. ### Prerequisites * Python 3.10+ * [uv](https://docs.astral.sh/uv/) (recommended) or `pip` ### Install the latest stable release ```bash uv icon="terminal" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} uv pip install --upgrade geneva ``` ```bash pip icon="terminal" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} pip install --upgrade geneva ``` ### Verify ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} python -c "import geneva; print(geneva.__version__)" ``` ### Pre-release builds To pick up the newest features ahead of a stable release, install a pre-release from LanceDB's Fury indexes. Geneva and its dependencies are published across two indexes: | Package | Index | | ------------------- | ------------------------------------ | | `geneva`, `lancedb` | `https://pypi.fury.io/lancedb/` | | `pylance` | `https://pypi.fury.io/lance-format/` | ```bash uv icon="terminal" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} uv pip install --pre --upgrade \ --extra-index-url https://pypi.fury.io/lancedb/ \ --extra-index-url https://pypi.fury.io/lance-format \ --index-strategy unsafe-best-match \ geneva ``` ```bash pip icon="terminal" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} pip install --pre --upgrade \ --extra-index-url https://pypi.fury.io/lancedb/ \ --extra-index-url https://pypi.fury.io/lance-format \ geneva ``` The `--index-strategy unsafe-best-match` flag is required with `uv`. By default, `uv` only considers package versions from the first index that lists a given package (PyPI). Since `geneva` and `pylance` also appear on PyPI, this flag tells `uv` to pick the best match across all indexes. ## Quickstart ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import os import geneva import pyarrow as pa # Connect to LanceDB Enterprise db = geneva.connect( uri="db://my-db", host_override=os.getenv("LANCEDB_URI", "http://localhost:10024"), api_key=os.getenv("LANCEDB_API_KEY"), ) tbl = db.open_table("my_table") # Define a User Defined Function (UDF) that counts the words in the text column @geneva.udf(data_type=pa.int32()) def word_count(text: str) -> int: return len(text.split()) # Register the UDF as a new virtual column tbl.add_columns({"word_count": word_count}) # Backfill the new column using distributed execution with incremental checkpointing tbl.backfill("word_count") ``` ## Auto-backfill With `auto_backfill=True`, LanceDB Enterprise recomputes the column for you whenever the data or the UDF version changes — no explicit `backfill()` call needed (see [Backfilling](/geneva/jobs/backfilling/)). ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Change the column to use a new UDF version with auto-backfill enabled @geneva.udf(data_type=pa.int32(), auto_backfill=True) def word_count(text: str) -> int: return len(text.split()) tbl.alter_columns({"path": "word_count", "udf": word_count}) # Add new rows. word_count is computed automatically in the background. tbl.add([{"text": "hello world"}]) ``` ## Materialized views and chunkers A [materialized view](/geneva/jobs/materialized-views/) applies UDFs over a query and refreshes incrementally. A [chunker](/geneva/udfs/scalar-udtfs) view expands each source row into many rows (1:N) — useful for splitting documents, videos, or images. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Materialized view: a query with UDF-computed columns, refreshed incrementally query = tbl.search(None).select({"text": "text", "word_count": word_count}) view = db.create_materialized_view("my_view", query) view.refresh() # Chunker view: 1:N row expansion — split each row's text into one row per word from typing import Iterator, NamedTuple class Chunk(NamedTuple): chunk_index: int chunk_text: str @geneva.chunker def split_text(text: str) -> Iterator[Chunk]: for i, word in enumerate(text.split()): yield Chunk(chunk_index=i, chunk_text=word) chunks = db.create_udtf_view( "my_chunks", source=tbl.search(None).select(["text"]), udtf=split_text, ) chunks.refresh() ``` ## Connecting to object storage or a local filesystem Geneva can also run directly against cloud object storage or a local path. In this mode, jobs run on a [distributed execution context](/geneva/jobs/contexts) you provide. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Cloud object storage (S3, GCS, Azure, or any S3-compatible object store) db = geneva.connect("s3://my-bucket/my-database") # Local filesystem db = geneva.connect("/path/to/my-database") ``` # Multimodal Feature Engineering with Geneva Source: https://docs.lancedb.com/geneva/index Learn how to do multimodal feature engineering in LanceDB Enterprise to transform raw data into meaningful features for AI models. Enterprise-only When working with multimodal data at scale, [LanceDB Enterprise](/enterprise) makes it easy to define, extract, and transform raw data into useful information and features for your AI applications. LanceDB Enterprise's *Multimodal Feature Engineering* package is designed to improve the productivity of AI engineers operating at immense scale. With an API designed to leverage LanceDB's optimized data storage and retrieval, it streamlines prototyping extraction and transformation tasks, performing experiments, exploring your data, scaling up execution, and moving to production. LanceDB Multimodal Feature Engineering enables researchers to seamlessly transition from experiments in local notebooks to fully-managed distributed job execution on datasets with billions of rows. Feature Engineering and the `geneva` Python package are currently only available as part of [LanceDB Enterprise](/enterprise). Please [contact us](mailto:contact@lancedb.com) if you're interested in scaling up your feature engineering workloads for your AI and multimodal use cases. The `geneva` package uses Python [User Defined Functions (UDFs)](/geneva/udfs/udfs) to define features as columns in a Lance dataset. Adding a feature is straightforward: Prototype your Python function in your favorite environment. Wrap the function with a small UDF decorator (see [UDFs](/geneva/udfs/udfs)). Register the UDF as a virtual column using `Table.add_columns()`. (Optional, advanced) Override where the job runs — see [Advanced Execution Contexts](/geneva/jobs/contexts). On LanceDB Enterprise, distributed job execution is fully managed, so most users can skip this step. Trigger a `backfill` operation (see [Backfilling](/geneva/jobs/backfilling/)). You can build your Python feature generator function in an IDE or a notebook using your project's Python versions and dependencies. `geneva` will automate much of the dependency and version management needed to move from prototype to scale and production. Ready to write your first feature? Head to [Getting Started](/geneva/getting-started). ## Continue learning Visit the following pages to learn more about featuring engineering in LanceDB Enterprise: * **Get started**: [Getting Started](/geneva/getting-started) · [What is Feature Engineering?](/geneva/overview/) · [End-to-end example](/geneva/end-to-end) * **UDFs**: [Using UDFs](/geneva/udfs/udfs) · [Blob helpers](/geneva/udfs/blobs/) · [Error handling](/geneva/udfs/error_handling) · [Advanced configuration](/geneva/udfs/advanced-configuration) * **Jobs**: [Job execution overview](/geneva/jobs/) · [Backfilling](/geneva/jobs/backfilling/) · [Materialized views](/geneva/jobs/materialized-views/) · [Startup optimizations](/geneva/jobs/startup/) · [Advanced job configuration](/geneva/jobs/advanced-job-configuration/) · [Advanced execution contexts](/geneva/jobs/contexts/) · [Geneva console](/geneva/jobs/console) · [Performance](/geneva/jobs/performance/) * **Deployment**: [Deployment overview](/geneva/deployment/) · [Helm deployment](/geneva/deployment/helm/) · [Troubleshooting](/geneva/deployment/troubleshooting/) ## API Reference * [`geneva.connect()`](https://lancedb.github.io/geneva/api/) — connect to a Geneva database * [Connection](https://lancedb.github.io/geneva/api/connection/) — manage tables, views, jobs, clusters, and manifests * [Table](https://lancedb.github.io/geneva/api/table/) — add columns, backfill, search, and manage table data * [UDF](https://lancedb.github.io/geneva/api/udf/) — define user-defined functions for feature computation # Advanced Job Configuration Source: https://docs.lancedb.com/geneva/jobs/advanced-job-configuration Pin the dependency manifest a transform's distributed job runs with using @udf, @chunker, and @udtf. Enterprise-only On LanceDB Enterprise, backfill and refresh jobs run on a managed, distributed execution environment configured at deployment time: * the **default cluster** — the compute pool jobs run on, and * the **default manifest** — the Python dependency environment (image and packages) the distributed workers run with. These defaults are set in the [LanceDB Helm chart](/geneva/deployment/helm) and cover most workloads. When a transform needs dependencies that differ from the deployment default, pin a **manifest** on the transform itself, as described below. To override the **cluster** a job runs on — for example to route an embedding backfill to a GPU pool — see [Advanced Execution Contexts](/geneva/jobs/contexts). ## Pinning a dependency manifest A manifest pins the Python image and packages the distributed workers run with. Build one with the manifest builders, then attach it to your transform with the `manifest=` argument on `@udf`, `@chunker`, or `@udtf`. The manifest is snapshotted into the column (or view) metadata when the transform is registered, so every backfill or refresh of that transform uses it automatically — there is no per-call manifest argument to remember. **Manifests are immutable at the column / view level.** When a transform is registered, its manifest is snapshotted onto the column (or view) metadata. Changing the deployment-default manifest — or the `GenevaManifest` object in your code — does **not** affect existing columns or views: they keep using the snapshot taken at creation time. To move a column or view to a new manifest, re-point it to a new (or updated) UDF / chunker / UDTF — for example with `alter_columns()` for a column, or by recreating the view. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow as pa from typing import Iterator, NamedTuple from geneva import udf, chunker, udtf from geneva.manifest import GenevaManifest # Build a manifest that pins the dependencies these transforms need embed_manifest = ( GenevaManifest.create_pip("embedding-deps") .pip(["sentence-transformers==3.3.1", "torch==2.5.1"]) .build() ) ``` ### `@udf(manifest=...)` Pin dependencies for a 1:1 computed column: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @udf(data_type=pa.list_(pa.float32(), 384), manifest=embed_manifest) def embed(text: str) -> list[float]: from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-small-en-v1.5") return model.encode(text, normalize_embeddings=True).tolist() tbl.add_columns({"embedding": embed}) tbl.backfill("embedding") # the backfill job runs with embed_manifest ``` ### `@chunker(manifest=...)` Pin dependencies for a 1:N [chunker](/geneva/udfs/scalar-udtfs) (scalar UDTF): ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} class Chunk(NamedTuple): chunk_index: int chunk_text: str @chunker(manifest=embed_manifest) def split_document(text: str) -> Iterator[Chunk]: for i, part in enumerate(text.split("\n\n")): yield Chunk(chunk_index=i, chunk_text=part) view = db.create_udtf_view("chunks", source=tbl.search(None), udtf=split_document) view.refresh() # the refresh job runs with embed_manifest ``` ### `@udtf(manifest=...)` Pin dependencies for an N:M [batch UDTF](/geneva/udfs/batch-udtfs): ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @udtf( output_schema=pa.schema([ pa.field("label", pa.string()), pa.field("count", pa.int64()), ]), manifest=embed_manifest, ) def group_stats(source) -> Iterator[pa.RecordBatch]: df = source.to_pandas() agg = df.groupby("label").size().reset_index(name="count") yield pa.RecordBatch.from_pandas(agg) view = db.create_udtf_view("summaries", source=tbl.search(None), udtf=group_stats) view.refresh() # the refresh job runs with embed_manifest ``` ## Capturing your local environment for testing When iterating locally, you often want the workers to run with the *exact* packages from your current environment rather than a curated pip list. `Connection.capture_local_environment()` zips your workspace (and, optionally, your site-packages), uploads the archives through the connection, and returns a ready-to-use `GenevaManifest` you can attach to a transform with `manifest=`. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import os import pyarrow as pa import geneva from geneva import udf db = geneva.connect( uri="db://my-db", host_override=os.getenv("LANCEDB_URI"), api_key=os.getenv("LANCEDB_API_KEY"), ) # Capture the local workspace; rely on the worker image for site-packages manifest = db.capture_local_environment(skip_site_packages=True) @udf(data_type=pa.string(), manifest=manifest) def shout(text: str) -> str: return text.upper() tbl = db.open_table("my_table") tbl.add_columns({"shout": shout}) tbl.backfill("shout") # workers run with your captured environment ``` Pass `skip_site_packages=False` (the default) to also upload your local site-packages. ## Manifest resolution For a given transform, the manifest is resolved in this order (first match wins): 1. The manifest pinned on the transform via `@udf` / `@chunker` / `@udtf` `manifest=`. 2. For a materialized view, the manifest snapshotted on the view when it was created. 3. The deployment-default manifest from the [LanceDB Helm chart](/geneva/deployment/helm). The `manifest=` argument applies to managed enterprise (`db://`) jobs. For direct object-storage or local-filesystem connections, configure the dependency environment explicitly with an [Advanced Execution Context](/geneva/jobs/contexts) instead. # Backfilling Source: https://docs.lancedb.com/geneva/jobs/backfilling Learn how to trigger backfill operations to populate column values in your LanceDB table using Geneva's distributed framework. ## Triggering Backfill Triggering backfill creates a distributed job to run the UDF and populate the column values in your LanceDB table. The Geneva framework simplifies several aspects of distributed execution. **Checkpoints**: Each batch of UDF execution is checkpointed so that partial results are not lost in case of job failures. Jobs can resume and avoid most of the expense of having to recalculate values. ## Auto-backfill Computed columns can be explicitly backfilled or they can be configured to be backfilled automatically as data changes. Set `auto_backfill=True` on the UDF, and the column is automatically recomputed whenever it falls out of sync with its source data. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Mark the column's UDF for automatic backfill @udf(data_type=pa.list_(pa.float32(), 1536), version="1", auto_backfill=True) def embed_udf(text: str) -> list[float]: return embedding_model.encode(text) tbl.add_columns({"embedding": embed_udf}) # No explicit backfill() needed — adding rows triggers recomputation automatically tbl.add(new_rows) ``` The `auto_backfill` flag is recorded in the column metadata when the column is added or altered. LanceDB Enterprise's managed agent watches for columns that need recomputation and dispatches a distributed backfill job for you — there is no manual trigger and no status polling. A column is recomputed when, for example, **new rows are added** (leaving it null for those rows) or the **UDF version changes** (you bump `version=` and `alter_columns()` to the new function). Auto-backfill is an enterprise feature. On direct object-storage or local-filesystem connections there is no managed agent, so `auto_backfill=True` has no effect and you must run `backfill()` explicitly. ## Adaptive checkpoint sizing Geneva can automatically adjust checkpoint sizes during a backfill. It starts with small checkpoints (faster proof-of-life) and grows them as it observes stable throughput, while staying within safe bounds. Planning still uses your configured checkpoint size (`checkpoint_size`), but the actual checkpoint chunks can be smaller when adaptive sizing is enabled. Adaptive sizing is always clamped to bounds: * `max_checkpoint_size`: Upper bound. Defaults to the job's checkpoint size (`checkpoint_size`) and is capped at that value if you set a larger max. * `min_checkpoint_size`: Lower bound. Defaults to 1. When `min_checkpoint_size == max_checkpoint_size`, adaptive sizing is disabled and checkpoints are fixed-size. You can set adaptive bounds in two places: * On the UDF definition via `@udf(..., min_checkpoint_size=..., max_checkpoint_size=...)` * On the backfill call via `table.backfill(..., min_checkpoint_size=..., max_checkpoint_size=...)` Backfill-level values take precedence over UDF defaults. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @udf(min_checkpoint_size=25, max_checkpoint_size=200) def embed_udf(text): ... # Override the UDF defaults for this run tbl.backfill("embedding", min_checkpoint_size=10, max_checkpoint_size=100) ``` ## Managing concurrency One way to speed up the execution of a job to give it more resources and to have it work in parallel. There are a few settings you can use on the backfill command to tune this. * process-level `concurrency` * thread-level `intra_applier_concurrency` Process level concurrency can be set with the `concurrency` parameter. This lets you specify the number of processes calculating values using the UDF. The default is 8 and should be set to the number of GPUs you would like to dedicate to your job. This can also be used based on CPU constraints. So if you have 40 machines with 4 GPUs each, you could set ths value to 160. If you set the value higher than the resources available, Geneva will try to schedule as much of the resources as it can (and potentially auto-scale to get more resources). Thread level concurrency can be set with the `intra_applier_concurrency` parameter. This lets you specify the number of threads in each process is calculating values using the UDF. The default is 1. If you have CPU heavy jobs this may be the best setting to tweak to get more utilization out of your systems. If you set the value higher than the resources available, Geneva will try to schedule as much of the resources as it can get. The two settings can be used in combination. So if your UDF requires 1 CPU and you set `concurrency` to 10 and `intra_applier_concurrency` to 5, you will potentially have 50 instances of the UDFs running in parallel. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # backfill embeddings with 10 * 5 = 50 instances tbl.backfill("embedding", concurrency=10, intra_applier_concurrency=5) ``` ## Managing commit visibility Feature engineering jobs at scale can take days to complete. Two settings can help you present progress to other readers incrementally. * Limit the number of rows processed with `num_frags` * Perform intermediate commits with `commit_granularity` The `num_frags` parameter lets you limit the number of fragments processed before the job is considered complete. If you have a table with 1000 fragments, you could set `num_frags` to 1 to see how your UDF performs and if to validate the values generated. You can then later run with a larger `num_frags` value or without the `num_frags` setting to complete the backfill. Any fragments prevoiusly computed are not computed again. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # only backfill 2 fragments so experiement can be done on the sample tbl.backfill("embedding", num_frags=2) ``` The `commit_granularity` parameter lets you specify how many fragments need to be ready to commit before a intermediate commit occurs and makes partial results visible to other readers. So for our example with a table of 1000 fragments, you can set `commit_granularity` to 10 to see progress updates every 10 fragments. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # backfill all fragments and perform an intermediate commit every 10 fragments to expose incremental results. tbl.backfill("embedding", commit_granularity=10) ``` ## Filtered Backfills Geneva allows you to specify SQL-style filters on the backfill operation. This lets you to apply backfills to a specified subset of the table's rows. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # only backfill video content whose filenames start with 'a' tbl.backfill("content", where="starts_with(filename, 'a')") # only backfill embeddings of only those videos with content tbl.backfill("embedding", where="content is not null") ``` Geneva also allows you to incrementally add more rows or have jobs that just update rows that were previously skipped. If new rows are added, we can run the same command and the new rows that meet the criteria will be updated. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # only backfill video content whose filenames start with 'a' tbl.backfill("content", where="starts_with(filename, 'a')") # only backfill embeddings of only those videos with content tbl.backfill("embedding", where="content is not null") ``` Or, you can use filters to add in or overwrite content in rows previously backfilled. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # only backfill video content whose filenames start with 'a' or 'b' but only if content not pulled previously tbl.backfill("content", where="(starts_with(filename, 'a') or starts_with(filename, 'b')) and content is null") # only backfill embeddings of only those videos with content and no prevoius embeddings tbl.backfill("embedding", where="content is not null and embeddding is not null") ``` Reference: * [`backfill` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.backfill) * [`backfill_async` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.backfill_async) * [`plan_backfill` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.plan_backfill) * [UDF](https://lancedb.github.io/geneva/api/udf/) — `@udf` decorator and UDF configuration options including `checkpoint_size` # Bulk Loading & Updating Columns Source: https://docs.lancedb.com/geneva/jobs/bulk-load-columns Load or update column data from external sources (Parquet, Lance, IPC) into your LanceDB table using a primary-key join. Beta — introduced in Geneva 0.13.0 ## Overview A common scenario is having column data that **already exists** in an external dataset — embeddings from a vendor, features exported from a data warehouse, or columnar data in cloud storage — that you want to load into an existing LanceDB table. `load_columns` joins value columns from an external source into your table by primary key. It works for both use cases: * **Adding new columns:** If the specified columns don't exist in the destination table, they are created automatically. * **Updating existing columns:** If the columns already exist, matched rows are updated with the source values. Unmatched rows are controlled by the `on_missing` parameter. **Destination table (before):** | pk | col\_a | col\_b | | -- | ------ | ------ | | 1 | x | 10 | | 2 | y | 20 | | 3 | z | 30 | **External source (Parquet / Lance / IPC):** | pk | embedding | | -- | --------- | | 1 | \[.1, .2] | | 2 | \[.3, .4] | | 3 | \[.5, .6] | **Destination table (after `load_columns` join on `pk`):** | pk | col\_a | col\_b | embedding | | -- | ------ | ------ | --------- | | 1 | x | 10 | \[.1, .2] | | 2 | y | 20 | \[.3, .4] | | 3 | z | 30 | \[.5, .6] | ### When to use * **Loading new columns:** Attach pre-computed embeddings from a vendor, or add features exported from Spark/BigQuery as Parquet. * **Updating existing columns:** Replace outdated embeddings with a newer model's output, or refresh feature values from an updated export. * **Partial updates:** Update a subset of rows (e.g., only rows whose embeddings were recomputed) while preserving all other values via carry semantics. * **Format consolidation:** Merge columnar data spread across Parquet files into an existing Lance table. ## Basic usage Supports Parquet, Lance, and IPC sources. The format is auto-detected from the URI suffix, or can be overridden with `source_format`. You can load one or more columns in a single call. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import lancedb db = lancedb.connect("my_db") table = db.open_table("my_table") # Add a new embedding column from a Parquet source table.load_columns( source="s3://bucket/embeddings/", pk="document_id", columns=["embedding"], ) # Later, update the same column with refreshed embeddings table.load_columns( source="s3://bucket/embeddings_v2/", pk="document_id", columns=["embedding"], ) ``` For non-blocking execution, use `load_columns_async` which returns a `JobFuture` — call `.result()` to block until completion. ## Handling missing keys When the source doesn't cover every row in the destination, the `on_missing` parameter controls what happens to unmatched rows: | Mode | Behavior | | ------------------- | ----------------------------------------------- | | `"carry"` (default) | Keep existing value. NULL if the column is new. | | `"null"` | Explicitly set to NULL. | | `"error"` | Raise an error on the first unmatched row. | ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Default: unmatched rows keep their current value table.load_columns( source="s3://bucket/partial_embeddings/", pk="document_id", columns=["embedding"], on_missing="carry", ) # Strict mode: fail if source doesn't cover all rows table.load_columns( source="s3://bucket/embeddings/", pk="document_id", columns=["embedding"], on_missing="error", ) ``` The `carry` mode is particularly important for partial and multi-pass loads — it ensures that previously loaded values are never overwritten. ## Performance tuning ### Concurrency The `concurrency` parameter controls the number of worker processes. The default is 8 — set this to match your available cluster resources. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} table.load_columns( source="s3://bucket/embeddings/", pk="document_id", columns=["embedding"], concurrency=16, ) ``` ### Checkpointing Bulk load jobs checkpoint each batch for fault tolerance, using the same infrastructure as [backfill jobs](/geneva/jobs/backfilling#adaptive-checkpoint-sizing). Key parameters: * `checkpoint_interval_seconds`: Target seconds per checkpoint batch (default 60s). The adaptive sizer grows or shrinks batch sizes to hit this target. * `min_checkpoint_size` / `max_checkpoint_size`: Bounds for adaptive sizing. If your job is small enough to complete without needing fault tolerance, you can get better performance by effectively disabling checkpoints. Increase `checkpoint_interval_seconds` to a large value and set `min_checkpoint_size` high enough that each worker processes its entire workload in a single batch. ### Commit visibility For long-running jobs, `commit_granularity` controls how many fragments complete before an intermediate commit makes partial results visible to readers. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} table.load_columns( source="s3://bucket/embeddings/", pk="document_id", columns=["embedding"], commit_granularity=10, ) ``` ### Multi-pass loads for large sources `load_columns` builds an in-memory primary-key index from the source. If the source is too large to fit in memory, split it into chunks and run sequential calls. Carry semantics guarantee correctness across passes. Index memory depends on primary key type: | PK type | \~Memory per row | 100M rows | 1B rows | | --------------------- | ---------------- | --------- | ------- | | int64 | \~8 bytes | \~800 MB | \~8 GB | | string (avg 32 bytes) | \~32 bytes | \~3.2 GB | \~32 GB | Choose N so that `source_size / N` fits in memory: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import pyarrow.dataset as pads # Discover source files (metadata only, no data I/O) source_files = pads.dataset("s3://bucket/embeddings/", format="parquet").files # Split into N chunks and run sequentially N = 4 total = len(source_files) for i in range(N): chunk = source_files[i * total // N : (i + 1) * total // N] table.load_columns( source=chunk, pk="document_id", columns=["embedding"], ) ``` Each pass reads only its assigned files, so total source I/O stays at 1x. If the source is already partitioned into subdirectories, pass each URI directly: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} for shard in range(4): table.load_columns( source=f"s3://bucket/embeddings/shard_{shard}/", pk="document_id", columns=["embedding"], ) ``` Multi-pass loads must run **sequentially**, not concurrently. Two `load_columns` calls running at the same time against the same column produce an interleaved end state. Use a plain `for` loop, not `concurrent.futures`. ## Reference * [`load_columns` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.load_columns) * [`load_columns_async` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.load_columns_async) # Backfill Conflicts Source: https://docs.lancedb.com/geneva/jobs/conflicts Learn how Geneva handles conflicts during backfill operations and what to do when they occur. ## Overview Geneva backfills operate on a **point-in-time snapshot** of your table. When other operations modify the table during or between backfills, conflicts can occur. Geneva >=0.9.0 automatically handles most conflict scenarios, reducing unnecessary recomputation and enabling graceful recovery. ## Safe Operations During Backfill These operations can safely run while a backfill is in progress: | Operation | Why It's Safe | | -------------------------------------- | ----------------------------------------------------- | | `merge_insert` (Insert-only) | Creates new fragments without modifying existing ones | | `add()` / append data | Creates new fragments without modifying existing ones | | Read operations (`search`, `to_arrow`) | Read-only, no fragment modification | | Adding new columns | Schema change only, no fragment rewrite | ## Operations That Cause Conflicts These operations can conflict with running backfills: | Operation | What Happens | | -------------------------------- | ----------------------------------------------------------- | | `compact_files()` / `optimize()` | Reorganizes fragments, invalidating the backfill's snapshot | | `merge_insert` with updates | Modifies existing rows, causing fragment conflicts | | `delete()` | Modifies existing fragments | When a conflict occurs, affected fragments fail gracefully. The backfill completes what it can, and you can re-run it to process the remaining rows. ## How Geneva Handles Conflicts ### Concurrent Backfills on Different Columns When multiple backfills run on the same table but different columns, Geneva handles version conflicts automatically: 1. Each backfill writes to different column files (field IDs) 2. If a commit conflict occurs, Geneva retries at the latest version 3. The retry merges the new column data without overwriting other columns This is controlled by the `GENEVA_VERSION_CONFLICT_MAX_RETRIES` environment variable (default: 10). See [Advanced Configuration](/geneva/udfs/advanced-configuration) for details. ### Compaction Between Backfills When you run compaction between backfills (not during), Geneva handles it efficiently: | Scenario | Behavior | | ------------------------------------------------ | ----------------------------------------------------------- | | Backfill, compact, re-backfill (same UDF) | Already-computed rows are skipped via `WHERE IS NULL` | | Partial backfill, compact, resume | Incremental processing continues from where it left off | | Backfill, `alter_columns` (new UDF), re-backfill | Full reprocessing with new UDF (intentional) | Geneva's default behavior is to skip rows that already have values (`WHERE IS NULL`). This means compaction doesn't cause unnecessary recomputation. ## Recovery Steps When a conflict occurs during a backfill: 1. **Wait** for any concurrent operations (compaction, updates) to complete 2. **Re-run** the backfill: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} tbl.backfill("column_name") ``` 3. **Only uncomputed rows** will be processed (rows with NULL values in the target column) Checkpoints from the previous run are preserved, so you won't lose progress on successfully computed rows. ## Best Practices ### Sequence Your Operations For the smoothest experience, sequence your operations: ``` 1. Complete all data ingestion 2. Run backfill to compute UDF columns 3. Run compaction/optimization ``` ### Use Insert-Only Operations During Backfill If you need to add data while a backfill is running, use insert-only operations: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Safe: INSERT-only merge_insert tbl.merge_insert("id").when_not_matched_insert_all().execute(new_data) # Unsafe: Updates to existing rows tbl.merge_insert("id").when_matched_update_all().execute(data) # May conflict ``` ### Monitor Backfill Progress Use async backfills to monitor progress and handle errors: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} fut = tbl.backfill_async("column_name") while not fut.done(): time.sleep(1) # Check for errors before subsequent operations result = fut.result() ``` ### Disable Auto-Compaction During Large Backfills If using LanceDB Enterprise which has auto-compaction enabled, consider disabling it during large backfill operations to avoid conflicts. ## Related * [Backfilling](/geneva/jobs/backfilling) - Triggering and configuring backfill operations * [Advanced Configuration](/geneva/udfs/advanced-configuration) - Environment variables for retry behavior ## API Reference * [Table](https://lancedb.github.io/geneva/api/table/) — `backfill()`, `add()`, `merge_insert()`, and other table mutation methods # Geneva Console Source: https://docs.lancedb.com/geneva/jobs/console The Geneva Console provides a web-based interface for monitoring and managing Geneva jobs, clusters, and manifests. geneva-console ## Why a Geneva Console? * Collaboration: The console helps multiple people work together. Individual jobs can be run in a notebook or workflow, but to collaborate on jobs, it helps to be able to see everything that's running on a given database. * History: See what has run in the past and diagnose any problems with your jobs. * Shared resources: The console stores definitions of clusters and manifests, so you can easily tell what resources you want to use to run your job. ## Getting Started The Geneva console is installed with the Geneva Helm chart; [contact LanceDB](https://lancedb.com/contact/) for access to the Helm chart. 1. Install or upgrade the Geneva Helm chart (see [Helm Deployment](/geneva/deployment/helm/)). 2. In your web browser, connect to the Geneva Console UI using the external ingress/load balancer URI configured in your deployment. 3. **Backup (no external ingress):** if your deployment doesn't expose the console via ingress or a load balancer, forward port 3000 from the `geneva-console-ui` service and open `http://localhost:3000`: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} kubectl port-forward -n lancedb svc/geneva-console-ui 3000:3000 ``` (Use `-n` to specify the namespace you installed the Helm chart into. We advise `lancedb`.) 4\. When prompted, enter your bucket and database, like: ``` s3://my-bucket/my-db ``` ## What's in the Console? ### Jobs Overview The heart of the console is an overview of all jobs that are running on a given database. See each job's status, progress, timing, and initiator. ### Job Details Click on a job's ID to get more details, especially events that have happened in a job's life cycle, and metrics such as number of workers, rows, and fragments written. ### Clusters See the Geneva clusters that you have defined to run jobs. Because clusters can be reused by name, this view can help you run a new job with the same resource constraints as a previous job. ### Manifests See the Manifests you've defined and what packages/dependencies they contain. As with clusters, manifests are reusable, so it's easy to start a new job with the same dependencies as an old one by just specifying the manifest name. ## API Reference * [Connection](https://lancedb.github.io/geneva/api/connection/) — `get_job()`, `list_jobs()`, `list_clusters()`, `list_manifests()` * [Cluster](https://lancedb.github.io/geneva/api/cluster/) — `GenevaCluster` and cluster configuration classes * [Manifest](https://lancedb.github.io/geneva/api/manifest/) — `GenevaManifest` and manifest builder classes # Advanced Execution Contexts Source: https://docs.lancedb.com/geneva/jobs/contexts Configure the distributed execution backend — clusters and dependency manifests — for Geneva jobs on object-storage and local-filesystem connections. **This page applies to direct object-storage and local-filesystem connections only.** On LanceDB Enterprise (`db://`) connections, distributed job execution is fully managed: the cluster and manifest are configured at deployment time (see [Helm deployment](/geneva/deployment/helm)) and can be overridden per job with `cluster=` / `manifest=` (see [Advanced Job Configuration](/geneva/jobs/advanced-job-configuration)). You do not define execution contexts there. When you connect Geneva directly to object storage (`s3://`, `gs://`, …) or a local path, there is no managed control plane, so you configure the execution backend yourself using the contexts below. The APIs on this page require Geneva **v0.10.0** or later. Geneva's distributed execution backend is **Ray**. There are 3 ways to connect to a Ray cluster: 1. Local Ray 2. KubeRay: create a cluster on demand in your Kubernetes cluster. 3. Existing Ray Cluster ## Ray Clusters ### Local Ray To execute jobs without an external Ray cluster, you can use `LocalRayContext`. This will auto-create a Ray cluster on your machine. Because it's on your laptop/desktop, this is only suitable for prototyping on small datasets. But it is the easiest way to get started. Simply define the UDF, add a column, call [`Connection.local_ray_context()`](https://lancedb.github.io/geneva/api/connection/#geneva.db.Connection.local_ray_context), and trigger the job: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva import udf from geneva.db import Connection @udf def filename_len(filename: str) -> int: return len(filename) tbl.add_columns({"filename_len": filename_len}) with Connection.local_ray_context(): tbl.backfill("filename_len") ``` Geneva will package up your local environment and send it to each worker process, so they'll have access to all the same dependencies as if you ran a simple Python script yourself. ### KubeRay If you have a Kubernetes cluster with kuberay-operator, you can use Geneva to automatically provision RayClusters. To do so, define a Geneva cluster, representing the resource needs, Docker images, and other Ray configurations necessary to run your job. Make sure your cluster has adequate compute resources to provision the RayCluster. Here is an example Geneva cluster definition: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import geneva from geneva.cluster import GenevaCluster, K8sConfigMethod from geneva.cluster.builder import KubeRayClusterBuilder db = geneva.connect("s3://my-bucket/my-db") cluster_name = "my-geneva-cluster" # lowercase, numbers, hyphens only service_account = "my_k8s_service_account" # k8s service account that Geneva runs as k8s_namespace = "lancedb" # k8s namespace cluster = ( GenevaCluster.create_kuberay(cluster_name) .namespace(k8s_namespace) .aws_config(region="us-east-1") # only required if using AWS .config_method(K8sConfigMethod.LOCAL) # Load k8s config from `~/.kube.config` # (other options include EKS_AUTH to load from AWS EKS, or IN_CLUSTER to load the # config when running inside a pod in the cluster) .head_group( service_account=service_account, cpus=2, node_selector={"geneva.lancedb.com/ray-head":""}, # k8s label required for head in your cluster ) .add_worker_group( KubeRayClusterBuilder.cpu_worker() .cpus(4) .memory("8Gi") .service_account(service_account) .build() ) .add_worker_group( KubeRayClusterBuilder.gpu_worker() # defaults to 1 GPU .cpus(2) .memory("8Gi") .service_account(service_account) .build() ) .build() ) db.define_cluster(cluster_name, cluster) # define_cluster stores the cluster metadata in persistent storage. The Cluster can then be referenced by name and provisioned when creating an execution context. table = db.get_table("my_table") with db.context(cluster=cluster_name): table.backfill("my_udf") ``` See the API docs for all the parameters [`GenevaCluster.create_kuberay()`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.mgr.GenevaCluster.create_kuberay), [`add_worker_group()`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.KubeRayClusterBuilder.add_worker_group), [`cpu_worker()`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.KubeRayClusterBuilder.cpu_worker), and [`gpu_worker()`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.KubeRayClusterBuilder.gpu_worker) can use. #### Exit Modes When you launch multiple async jobs in a single context, the exit mode controls whether the cluster waits for all of them to finish and how it handles failures. You can customize this behavior with the `on_exit` parameter: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.runners.ray.raycluster import ExitMode with db.context(cluster=cluster_name, manifest=manifest_name, on_exit=ExitMode.DELETE): fut1 = tbl.backfill_async("embedding_a") fut2 = tbl.backfill_async("embedding_b") # No need to call .result() — the context waits for both jobs # Both jobs completed; cluster deleted ``` | Exit Mode | Behavior | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ExitMode.DELETE` (default) | Wait for all async jobs in the context to complete, then delete the cluster. Ideal for batch scripts that launch multiple `backfill_async()` calls in one context. | | `ExitMode.RETAIN_ON_FAILURE` | Wait for all async jobs in the context to complete. If any job failed, the context body raised an exception, or `wait_timeout` was exceeded, retain the cluster for debugging; otherwise delete. | | `ExitMode.RETAIN` | Never delete the cluster, regardless of job outcomes. Useful for notebooks and interactive sessions where you run multiple jobs over time. | ### External Ray cluster If you already have a Ray cluster, Geneva can execute jobs against it too. You do so by defining a Geneva cluster with [`GenevaCluster.create_external()`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.mgr.GenevaCluster.create_external) which has the address of the cluster. Here's an example: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import geneva from geneva.cluster import GenevaCluster db = geneva.connect(my_db_uri) cluster_name = "my-geneva-external-cluster" cluster = ( GenevaCluster.create_external(cluster_name, "ray://my_ip:my_port") .build() ) db.define_cluster(cluster_name, cluster) ``` If you need to send environment variables to your workers, in either a KubeRay or External Ray cluster, you can use [`ray_init_kwargs`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.KubeRayClusterBuilder.ray_init_kwargs), like so: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} cluster = ( GenevaCluster.create_kuberay(cluster_name) # or create_external(cluster_name) .ray_init_kwargs({ "runtime_env": { "env_vars": { "MY_VAR": "value", "AWS_ACCESS_KEY_ID": os.environ["AWS_ACCESS_KEY_ID"] }, }, }) ... .build() ) ``` ## Dependencies and Manifests Most UDFs require some dependencies: helper libraries like `pillow` for image processing, pre-trained models like `open-clip` to calculate embeddings, or even small config files. We have three ways to get them to workers: 1. Define dependencies explicitly in a manifest 2. Bake dependencies into an image 3. Auto-upload local dependencies ### Define dependencies explicitly in a manifest We recommend defining dependencies explicitly: it's the easiest way to understand exactly what's running, and the least error-prone. To do so, define a Manifest, like so: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.manifest import GenevaManifest db = geneva.connect(my_db_uri) manifest_name="dev-manifest" manifest = ( GenevaManifest.create_pip(manifest_name) .pip(["lancedb", "numpy"]) ).build() db.define_manifest(manifest_name, manifest) ``` After workers start up, this will run `pip install lancedb numpy` on them. You can also use [`.requirements_path(path)`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.PipManifestBuilder.requirements_path) to point to a local `requirements.txt` file instead of listing packages inline. Note that attempting to use both [`.pip()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.PipManifestBuilder.pip) and `.requirements_path()` will raise an exception. For conda-based dependencies, use [`GenevaManifest.create_conda()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.mgr.GenevaManifest.create_conda) instead: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.manifest import GenevaManifest manifest = ( GenevaManifest.create_conda("my-conda-manifest") .conda({"dependencies": ["python=3.10", "numpy"]}) ).build() ``` You can also use [`.conda_environment_path(path)`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.CondaManifestBuilder.conda_environment_path) to point to a local `environment.yml` file. Note that attempting to use both [`.conda()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.CondaManifestBuilder.conda) and `.conda_environment_path()` will raise an exception. ### Bake dependencies into an image Because the `pip` or `conda` methods involve installing packages, they will incur some startup costs. When your jobs are stable in production, therefore, it will be faster to build all your dependencies into the workers' images, then specify them like so: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.manifest import GenevaManifest db = geneva.connect(my_db_uri) manifest_name = "prod-manifest" manifest = ( GenevaManifest.create_pip(manifest_name) .worker_image("myregistry.example.com/my-custom-worker-image:latest") ).build() db.define_manifest(manifest_name, manifest) ``` You can also define images in a cluster via the [`head_group`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.KubeRayClusterBuilder.head_group) method and [`cpu_worker`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.CpuWorkerBuilder)/[`gpu_worker`](https://lancedb.github.io/geneva/api/cluster/#geneva.cluster.builder.GpuWorkerBuilder) methods, e.g.: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} cluster = ( GenevaCluster.create_kuberay(cluster_name) .head_group( image="myregistry.example.com/my-custom-head-image:latest", ) .add_worker_group(KubeRayClusterBuilder.cpu_worker() .image("myregistry.example.com/my-custom-cpu-worker-image:latest") ) .add_worker_group(KubeRayClusterBuilder.gpu_worker() .image("myregistry.example.com/my-custom-gpu-worker-image:latest") ) .build() ) ``` However, if an image is defined in both a Cluster and a Manifest, the definition in the Manifest will take priority. ### Auto-upload local dependencies Geneva can package your local environment and send it to Ray workers. This includes the current workspace root (if you're in a python repo) or the current working directory (if you're not). [`GenevaManifest.create_site()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.mgr.GenevaManifest.create_site) additionally uploads your Python site-packages (defined by `site.getsitepackages()`) to workers. This is not recommended for production use, as it is prone to issues like architecture mismatches of built dependencies, but it can be a good way to iterate quickly during development. To upload site packages: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva.manifest import GenevaManifest db = geneva.connect(my_db_uri) manifest_name = "dev-manifest" manifest = GenevaManifest.create_site(manifest_name).build() db.define_manifest(manifest_name, manifest) ``` ### What's in a manifest? Here's a summary of what's in a manifest and how you can define it across the three manifest builder types. | Contents | How you can define it | Factory method | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Local working directory (or workspace root, if in a python repo) | Will be uploaded automatically. | All | | Local python packages (site-packages) | Uploaded automatically. | [`create_site()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.mgr.GenevaManifest.create_site) | | Pip packages to be installed | Use [`.pip(packages: list[str])`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.PipManifestBuilder.pip) or [`.requirements_path(path: str)`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.PipManifestBuilder.requirements_path). See [Ray's RuntimeEnv docs](https://docs.ray.io/en/latest/ray-core/api/doc/ray.runtime_env.RuntimeEnv.html) for details. | [`create_pip()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.mgr.GenevaManifest.create_pip) | | Conda packages to be installed | Use [`.conda(deps: dict[str, Any])`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.CondaManifestBuilder.conda) or [`.conda_environment_path(path: str)`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.builder.CondaManifestBuilder.conda_environment_path). See [Ray's RuntimeEnv docs](https://docs.ray.io/en/latest/ray-core/api/doc/ray.runtime_env.RuntimeEnv.html) for details. | [`create_conda()`](https://lancedb.github.io/geneva/api/manifest/#geneva.manifest.mgr.GenevaManifest.create_conda) | | Local python packages outside of `site_packages` | Use `.py_modules(modules: list[str])` or `.add_py_module(module: str)`. See [Ray's RuntimeEnv docs](https://docs.ray.io/en/latest/ray-core/api/doc/ray.runtime_env.RuntimeEnv.html) for details. | All | | Container image for head node | Use `.head_image(head_image: str)` or `default_head_image()` to use the default. Note that, if the image is also defined in the GenevaCluster, the image set here in the Manifest will take priority. | All | | Container image for worker nodes | Use `.worker_image(worker_image: str)` or `default_worker_image()` to use the default for the current platform. As with the head image, this takes priority over any images set in the Cluster. | All | If you want to see exactly what is being uploaded to the cluster, set `.delete_local_zips(False)` and `.local_zip_output_dir(path)` then examine the zip files in `path`. ## Putting it all together: Execution Contexts An execution context represents the concrete execution environment (Cluster and Manifest) used to execute a distributed job. Calling `context` will enter a context manager that will provision an execution cluster and execute the Job using the Cluster and Manifest definitions provided. Because you've already defined the cluster and manifest, you can just reference them by name. Note that providing a manifest is optional. Once completed, the context manager will automatically de-provision the cluster. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} db = geneva.connect(my_db_uri) tbl = db.get_table("my_table") with db.context(cluster=cluster_name, manifest=manifest_name): tbl.backfill("embedding") ``` In a notebook environment, you can manually enter and exit the context manager in multiple steps like so: ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} ctx = db.context(cluster=cluster_name, manifest=manifest_name) ctx.__enter__() # ... do stuff ctx.__exit__(None,None,None) ``` ## API Reference * [Connection](https://lancedb.github.io/geneva/api/connection/) — `context()`, `local_ray_context()`, `list_clusters()`, `list_manifests()` * [Cluster](https://lancedb.github.io/geneva/api/cluster/) — `KubeRayClusterBuilder`, `LocalRayClusterBuilder`, `ExternalRayClusterBuilder`, and worker configuration classes * [Manifest](https://lancedb.github.io/geneva/api/manifest/) — `PipManifestBuilder`, `CondaManifestBuilder`, `SiteManifestBuilder` # Geneva Jobs Source: https://docs.lancedb.com/geneva/jobs/index Learn about Geneva's job execution framework for distributed feature engineering workflows. Geneva provides a comprehensive job execution framework for distributed feature engineering workflows. This section covers the different types of jobs and execution contexts available in Geneva. ## Job Types ### [Backfilling](/geneva/jobs/backfilling/) Trigger distributed jobs to populate column values in your LanceDB table using UDFs. Learn about filtered backfills and incremental updates. ### [Materialized Views](/geneva/jobs/materialized-views/) Create declarative materialized views to manage batch updates of expensive operations. Optimize data layouts for training and simplify orchestration. ### [Startup Optimizations](/geneva/jobs/startup/) Optimize job and session startup times for faster interactive development and production workflows. Learn about caching, pre-warming, and performance tuning. ## Execution Contexts ### [Advanced Execution Contexts](/geneva/jobs/contexts/) Understand how Geneva automatically packages and deploys your Python execution environment to worker nodes for distributed execution. ### [Geneva Console](/geneva/jobs/console/) Set up and access the Geneva Console for monitoring and managing Geneva jobs, clusters, and execution contexts. ## Key Features * **Distributed Processing**: Scale feature computation across multiple nodes * **Checkpointing**: Resume jobs from failures without losing progress * **Incremental Updates**: Only process new or modified data * **Distributed Execution**: Run jobs on Kubernetes or standalone compute clusters * **Environment Management**: Automatic dependency packaging and deployment ## Getting Started 1. **Choose your execution context** based on your infrastructure 2. **Define your UDFs** for feature computation 3. **Trigger backfill operations** to populate your data 4. **Monitor performance** and optimize based on usage patterns For detailed information about each job type and execution context, explore the documentation in this section. ## API Reference * [Table](https://lancedb.github.io/geneva/api/table/) — `backfill()`, `backfill_async()`, `refresh()`, `plan_backfill()`, `plan_refresh()`, and other job-triggering methods * [Connection](https://lancedb.github.io/geneva/api/connection/) — `get_job()`, `list_jobs()`, `context()`, and `local_ray_context()` * [Cluster](https://lancedb.github.io/geneva/api/cluster/) — configure KubeRay and local Ray execution backends * [Manifest](https://lancedb.github.io/geneva/api/manifest/) — package Python environments for remote workers # Job Metrics (Diagnostics) Source: https://docs.lancedb.com/geneva/jobs/job_metrics Use Geneva Job Metrics to monitor and troubleshoot jobs in real time. ## How to find metrics Job metrics can be found in the [Geneva Console UI](https://docs.lancedb.com/geneva/jobs/console), by clicking on a job's ID to get to the "Job details" page. ## Core diagnostic metrics | Metric | What it means | Common signal | | ---------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | | `rows_checkpointed` | Rows finished by read/UDF/checkpoint stage. | High value means upstream compute is progressing. | | `rows_ready_for_commit` | Rows ready for atomic commit (becoming visible to other DB connections). | If much lower than `rows_checkpointed`, writer path is likely bottlenecked. | | `rows_committed` | Rows already visible to other DB connections. | If lagging far behind `rows_ready_for_commit`, commit stage may be bottlenecked. | | `cnt_geneva_workers_active` | Current parallel UDF executors. | Lower than expected means reduced effective parallelism. | | `cnt_geneva_workers_pending` | Deficit from desired parallelism. | Persistently high value usually means scheduling/resource pressure. | | `read_io_time_ms` | Cumulative read IO time. | Dominant value suggests storage/read bottleneck. | | `udf_processing_time` | Cumulative UDF execution time. | Dominant value suggests compute/UDF bottleneck. | | `batch_checkpointing_time` | Cumulative batch checkpoint overhead. | High value suggests checkpoint overhead is expensive. | | `writer_write_time` | Cumulative writer output time. | High value often points to object storage throughput/throttling issues. | | `writer_queue_wait_time_ms` | Cumulative writer queue wait time. | High value can indicate writer starvation/backpressure. | | `commit_time_ms` | Cumulative commit time. | High value means commit itself is expensive. | | `commit_conflict_retries` | Commit retries due to version conflicts. | Non-trivial counts indicate commit contention. | | `commit_backoff_time_ms` | Time spent backing off during commit retries. | High value indicates contention/retry pressure. | | `commit_concurrent_writer_retries` | Retries from "Too many concurrent writers". | High value indicates writer concurrency contention. | ## Quick diagnosis workflow 1. Check `rows_checkpointed` vs `rows_ready_for_commit`. * If `rows_checkpointed` is high but `rows_ready_for_commit` is low, fragment writer is usually the bottleneck. * This often indicates object storage read/write pressure (for example S3). 2. Compare read, UDF, and checkpoint timing. * High `read_io_time_ms`: storage or scan bottleneck. * High `udf_processing_time`: UDF compute bottleneck. * High `batch_checkpointing_time`: checkpoint overhead bottleneck. * Typical mitigations: increase `checkpoint_size`, increase `max_checkpoint_size`, or compact the table to produce larger fragments. 3. Check writer timing. * High `writer_write_time` is commonly object storage throttling/throughput limit. * Typical mitigations: use higher network-bandwidth node types, and keep object storage and compute nodes in the same region. 4. Check commit pressure. * High `commit_conflict_retries`, `commit_backoff_time_ms`, or `commit_concurrent_writer_retries` indicates commit contention. 5. Check parallelism deficit. * If `cnt_geneva_workers_pending` stays high while `cnt_geneva_workers_active` stays low, the job is running below desired parallelism due to cluster/resource constraints. ## Notes * Timing metrics are cumulative and may overlap; do not sum them as exact wall time. * For completed jobs, row counters should settle to stable final values. # Job Lifecycle Source: https://docs.lancedb.com/geneva/jobs/lifecycle Understanding how Geneva jobs work, their lifecycle states, and how to monitor and manage them. Geneva uses background jobs to execute long-running operations like backfills and materialized view refreshes. This guide explains how jobs work, their lifecycle states, and how to monitor and manage them. ## Overview Jobs in Geneva are asynchronous operations that process data in the background. There are two primary job types: | Job Type | Purpose | Created By | | ----------------------------- | -------------------------------- | ------------------ | | **Backfill** | Compute column values using UDFs | `table.backfill()` | | **Materialized View Refresh** | Update precomputed query results | `view.refresh()` | Both job types share the same lifecycle states and monitoring capabilities. ## Job States Every job progresses through a well-defined state machine: ```mermaid theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} stateDiagram-v2 [*] --> PENDING PENDING --> RUNNING RUNNING --> DONE RUNNING --> FAILED DONE --> [*] FAILED --> [*] ``` | State | Description | | ----------- | ------------------------------------------------ | | **PENDING** | Job has been created and is queued for execution | | **RUNNING** | Job is actively processing data | | **DONE** | Job completed successfully | | **FAILED** | Job encountered an error during execution | ## Monitoring Jobs The [Geneva Console](/geneva/jobs/console) provides a web-based interface for monitoring job status, progress, and history across your database. This is the recommended way to track jobs in collaborative environments. For programmatic access, you can query job status directly via the API: ### Querying Job Status ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} import geneva db = geneva.connect("/path/to/db") # Get job state manager jsm = db._history # Get a specific job job = jsm.get(job_id)[0] print(f"Status: {job.status}") print(f"Started: {job.launched_at}") print(f"Completed: {job.completed_at}") # List jobs for a table pending_jobs = jsm.list_jobs(table_name="my_table", status="PENDING") running_jobs = jsm.list_jobs(table_name="my_table", status="RUNNING") ``` ### Progress Metrics Jobs report progress through metrics: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Access job metrics for metric in job.metrics: print(f"{metric['name']}: {metric['count']}/{metric['total']}") ``` Common metrics include: | Metric | Description | | --------------------- | ------------------------------------------------------------------------------------------ | | `fragments` | [Fragments](https://lance.org/format/table/?h=fragment#fragments) scheduled for processing | | `writer_fragments` | Fragments written to storage | | `udf_values_computed` | Rows processed by UDFs | | `rows_checkpointed` | Rows saved to checkpoint store | | `rows_committed` | Rows committed to the table | | `workers` | Workers started for parallel execution | ### Job Events Jobs log significant events during execution: ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} for event in job.events: print(f"{event['timestamp']}: {event['message']}") ``` Example events: * "Job started" * "Checkpointing complete for fragment 42" * "Partial commit: 64 fragments" * "Job completed successfully" ## Fault Tolerance Geneva jobs are designed to be resilient to failures: ### Checkpoint-Based Recovery Jobs save intermediate results to a checkpoint store. If a job fails: 1. **Completed work is preserved** - Checkpointed batches are not lost 2. **Resume from checkpoint** - Restarted jobs skip already-processed data 3. **No duplicate processing** - Each batch is processed exactly once By default, checkpoints are stored in a `_ckp/` subdirectory inside the table's storage location. At scale, you can redirect checkpoints to a separate bucket to avoid IOPS contention. See [Checkpoint Storage configuration](/geneva/udfs/advanced-configuration#checkpoint-storage) for details. ### Resuming Failed Jobs To resume a failed job, simply re-run the same backfill or refresh command. The job will automatically detect existing checkpoints, skip already-processed fragments, and continue from where it left off. ## API Reference * [Table](https://lancedb.github.io/geneva/api/table/) — `backfill()`, `backfill_async()`, `refresh()`, `JobFuture` * [Connection](https://lancedb.github.io/geneva/api/connection/) — `get_job()`, `list_jobs()` # Materialized Views with UDFs Source: https://docs.lancedb.com/geneva/jobs/materialized-views Learn how to use Geneva's materialized view feature to declaratively manage batch updates of expensive operations using UDFs. Geneva provides a materialized view feature that can be used to declaratively manage "batch" updates of expensive operations such as populating UDF columns. These updates are triggered via refresh operation. This can be used to optimize data layouts for training and to simplify some operations that traditionally may require external procedural orchestration (airflow, prefect, dagster). > **Note**: This is similar to how traditional databases offer a materialized view feature to declaratively manage expensive aggregation and join operations. ## Process Overview The process is straightforward: 1. Define a query on table, optionally including UDFs in the select clause. 2. Create the materialized view using `db.create_materialized_view(...)`. 3. Populate the new materialized view table using the `refresh` operation. Just like with backfills, this operation is incremental, checkpointed, and run in a distributed manner. ## Example Let's walk through an example using a raw video table as a base. We want to create a materialized view off the table that adds transcription columns to a subset of the values. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} from geneva import connect, udf import pyarrow as pa db = connect("/path/to/lancedb") schema = pa.schema([ pa.field("video_id", pa.int64(), nullable=False), pa.field("video_uri", pa.string(), nullable=False), pa.field("upload_ts", pa.timestamp("us"), nullable=False), pa.field("metadata", pa.json(), nullable=True), ]) raw_videos = db.create_table( "raw_videos", schema=schema, primary_key="video_id" ) ``` Here's our UDFs, and the creation of a new empty materialized view. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @udf def transcribe(video_uri) -> str: from whisper import load_model model = load_model("base") return model.transcribe(uri)["text"] @udf(data_type=pa.binary()) def load_video(video_uri: pa.Array) -> pa.Array: videos = return ... q = raw_videos.search(None) .shuffle(seed=42) .select( { "video_uri": "video_uri", "video": load_video, "transcription": transcribe, } ) view_table = db.create_materialized_view("table_view", q) ``` To populate the values, we call `refresh`. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # explicitly copy values from the source table, applying UDF on cols. db.refresh("table_view") ``` Note that the UDF is stored on the destination materialized view table. ```python Python icon="python" theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} raw_table.add(...) db.refresh("table_view") # only materialize new or modified rows. ``` The operation is incremental. So the next time refresh on the table is called, only new fragments with new data get materialized into the materialized view table. Materialized views are just tables so you can query them as well as modify them by adding new `add_columns`, `backfill` particular columns and deriving other materialized views or views from them. Reference: * [`create_materialized_view` API](https://lancedb.github.io/geneva/api/connection/#geneva.db.Connection.create_materialized_view) ## FAQ ### Do we copy the UDFs from the source table? No. The UDF does not but any UDF calculated values in the original table come to the materialized table via refresh. New columns defined by the UDFs in the materialized view creation are attached only to the materialized view. They can be backfilled (since the UDF belongs to the view) or refreshed. ### On MV refresh, do we force materialization of UDFs cols on the source table? No. They are managed at the source table only. If it is null the null values are propagated. Future options may force materialization/backfill "recursively". ## API Reference * [Connection](https://lancedb.github.io/geneva/api/connection/) — `create_materialized_view()`, `create_view()` * [Table](https://lancedb.github.io/geneva/api/table/) — `refresh()`, `plan_refresh()` * [Query](https://lancedb.github.io/geneva/api/query/) — `create_materialized_view()` on query builder # Distributed Job Performance and Cluster Sizing Source: https://docs.lancedb.com/geneva/jobs/performance Learn how to tune Geneva distributed job performance by scaling compute resources and balancing write bandwidth. When Geneva runs in distributed mode, jobs are deployed against a Kubernetes KubeRay instance that dynamically provisions a Ray cluster. Job execution time depends on sufficient CPU/GPU resources for *computation* and sufficient *write bandwidth* to store the output values. Tuning the performance of a job boils down to configuring the table or cluster resources. ## Geneva defaults Geneva sets the following defaults when creating a KubeRay cluster via GenevaClusterBuilder. These apply as both Kubernetes requests and limits. ### Head Node | Resource | Default | | --------------- | ----------------------------------- | | CPU | 4 | | Memory | 8 GiB | | GPU | 0 | | Node Selector | `geneva.lancedb.com/ray-head: true` | | Service Account | `geneva-service-account` | The head node runs the Ray GCS (Global Control Store), the driver task, and the dashboard. It does not run UDF applier actors. ### CPU Workers | Resource | Default | | ------------- | ----------------------------------------- | | CPU | 4 | | Memory | 8 GiB | | GPU | 0 | | Node Selector | `geneva.lancedb.com/ray-worker-cpu: true` | | Replicas | 1 (min: 0, max: 100) | | Idle Timeout | 60 seconds | ### GPU Workers | Resource | Default | | ------------- | ----------------------------------------- | | CPU | 8 | | Memory | 16 GiB | | GPU | 1 | | Node Selector | `geneva.lancedb.com/ray-worker-gpu: true` | | Replicas | 1 (min: 0, max: 100) | | Idle Timeout | 60 seconds | ## Scaling computation resources Geneva jobs can split and schedule computational work into smaller batches that are assigned to *tasks* which are distributed across the cluster. As each task completes, it writes its output into a checkpoint file. If a job is interrupted or run again, Geneva will look to see if a checkpoint for the computation is already present and if not will kick off computations. Usually computation capacity is the bottleneck for job execution. To complete all of a job's tasks more quickly, you just need to increase the amount of CPU/GPU resources available. ### Estimating CPU and memory requirements #### CPU Geneva can run up to `concurrency` tasks in parallel. Within each task, it can run up to `intra_applier_concurrency` UDF applications concurrently. If your UDF requests `udf.num_cpus` CPUs, the peak CPU required is approximately: `total_cpus ≈ concurrency * intra_applier_concurrency * udf.num_cpus` `concurrency` and `intra_applier_concurrency` are parameters on `Table.backfill(...)`. In most cases you do not need to change `udf.num_cpus`, unless the UDF itself uses multiple threads. If your UDF is single-threaded but you want to speed it up with more parallelism, prefer increasing `concurrency` first. If you stop seeing meaningful improvements, then try increasing `intra_applier_concurrency`. #### Memory Geneva defaults to checkpoint batches of up to 100 rows (`checkpoint_size=100`). Peak memory usage scales with both the input rows held for processing and the output rows buffered before they are written. A rough upper bound is: ```text theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} memory_bytes ≈ input_row_size * min(checkpoint_size * worker_num_cpus, task_size) + output_row_size * min(checkpoint_size * intra_applier_concurrency, task_size) ``` * `worker_num_cpus` is the number of CPUs on the Ray worker. * `task_size` is a tunable parameter (rows per task). It can be set on the UDF (e.g. `@udf(..., task_size=...)`) and on `Table.backfill(..., task_size=...)` (backfill-level values take precedence). If `task_size` is larger than the number of rows in a fragment, it behaves the same as `task_size = fragment_num_rows`. By default, it is: `task_size = num_rows / (concurrency * intra_applier_concurrency * 2)` (where `num_rows` is the number of rows in the table, or the number of rows selected by your filter) * `input_row_size` / `output_row_size` are the average bytes per row after materialization in memory. For primitive numeric columns (ints/floats), these sizes are usually small. For images, videos, embeddings, etc. you may need to explicitly provision more memory (e.g. via `@udf(..., memory=...)`) and/or reduce `checkpoint_size` / `task_size`. Typical per-row sizes: * Images: \~200KB–2MB (depends on dataset and encoding) * Videos: \~10MB–200MB * Embeddings: `dimension * data_type_size` bytes (e.g. float32 embeddings use 4 bytes per value, so a 1536-dim embedding is `1536 * 4 = 6144` bytes) #### Internal Actor Resource Overhead In addition to UDF applier actors, each backfill job creates internal actors that consume resources: | Component | CPU | Memory | Count | | -------------- | ------------------------------------------ | ---------- | ------------- | | Driver task | 0.1 | — | 1 | | JobTracker | 0.1 | 128 MiB | 1 | | Writer actors | 0.1 | 1 GiB | 1 per applier | | Queue actors | 0 | — | 1 per applier | | Applier actors | UDF `num_cpus * intra_applier_concurrency` | UDF memory | `concurrency` | #### Example For a job with `concurrency=4`, `intra_applier_concurrency=2`, with a UDF that requests 1 CPU and 1GiB memory: | Component | CPU | Memory | Count | | -------------- | --------------------------- | ----------------------------------------------- | ----- | | Driver task | 0.1 | — | 1 | | JobTracker | 0.1 | 128 MiB | 1 | | Writer actors | 0.1 | 1 GiB | 4 | | Queue actors | 0 | — | 4 | | Applier actors | 1\*2=2 | 1 GiB | 4 | | Total | 0.1+0.1+4*0.1+4*2 = **8.6** | 128 MiB + 4 \* 1GiB + 4 \* 1GiB = **8.125 GiB** | | ### Overall Cluster Sizing Of course, the size of a cluster will vary dramatically for each task. But if you don't know how to estimate your workload, we can recommend the following cluster sizes as a starting point: | Cluster Size | Head Node | Workers | | ---------------------- | ----------- | ----------------- | | **Small** (Dev / CI) | 1 CPU, 8 GB | 4 x 2 CPU, 8 GB | | **Medium** (Staging) | 2 CPU, 8 GB | 4 x 4 CPU, 8 GB | | **Large** (Production) | 4 CPU, 8 GB | 8+ x 8 CPU, 16 GB | ### Validation Thresholds The cluster builder validates memory configuration and warns or errors on suspicious values: | Threshold | Value | Behavior | | ---------------------------- | ------------------ | ------------------------------------------------- | | GPU worker minimum memory | `< 4 GiB` | **Error** — build fails if below this | | Large memory warning | `> 100 GB` | **Warning** — may exceed K8s node capacity | | Memory-per-CPU ratio warning | `> 16 GiB per CPU` | **Warning** — unusual ratio, likely misconfigured | ### GKE node pools GKE + KubeRay can autoscale the number of VM nodes on demand. Limitations on the amount of resources provisioned are configured via [node pools](https://cloud.google.com/kubernetes-engine/docs/how-to/node-pools#scale-node-pool). Node pools can be managed to scale vertically (type of machine) or horizontally (# of nodes). Properly applying Kubernetes labels to the node pool machines allows you to control resources for different jobs in your cluster. ### Options on `Table.backfill(..)` The `Table.backfill(..)` method has several optional arguments to tune performance. To saturate the CPUs in the cluster, the main arguments to change are `concurrency` which controls the number of task processes and `intra_applier_concurrency` which controls the number of task threads per task process. `commit_granularity` controls how frequently fragments are committed so that partial results can become visible to table readers. Setting `checkpoint_size` smaller introduces finer-grained checkpoints and can help provide more frequent proof of life as a job is being executed. This is useful if the computation on your data is expensive. Reference: * [`backfill` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.backfill) * [`backfill_async` API](https://lancedb.github.io/geneva/api/table/#geneva.table.Table.backfill_async) ## Balancing write bandwidth While computation can be broken down into small tasks, new Lance column data for each fragment must be written out in a serialized fashion. Each fragment has a writer that waits for checkpointed results to arrive, sequences them, and then serially writes out the new data file. Writers can be a bottleneck if a Lance dataset has a small number of fragments, especially if the amount of data being written out is comparatively large. Maximizing parallel write throughput can be achieved by having more fragments than nodes in the cluster. ### Symptom: Computation tasks complete but writers seem to hang Certain jobs that take a small dataset and expand it may appear as if the writer has frozen. An example is a table that contains a list of URLs pointing to large media files. This list is relatively small (\< 100MB) and can fit into a single fragment. A UDF that downloads will fetch all the data and then attempt to write all of it out through the single writer. This single writer can then be responsible for serially writing out 500+GB of data to a single file! To mitigate this, you can load your initial table so that there will be multiple fragments. Each fragment with new outputs can be written in parallel with higher write throughput. ## API Reference * [Cluster](https://lancedb.github.io/geneva/api/cluster/) — `KubeRayClusterBuilder`, `CpuWorkerBuilder`, `GpuWorkerBuilder` — configure CPU/GPU/memory resources * [Table](https://lancedb.github.io/geneva/api/table/) — `backfill()`, `compact_files()`, `optimize()` * [UDF](https://lancedb.github.io/geneva/api/udf/) — `@udf` decorator options: `num_cpus`, `num_gpus`, `memory`, `batch_size` # Job and Session Startup Optimizations Source: https://docs.lancedb.com/geneva/jobs/startup Learn how to optimize Geneva job and session startup times for faster interactive development and production workflows. During interactive sessions, there are two main actions where you would interact with Geneva. * Compute cluster creation * Job execution Behind the scenes, Geneva packages your python environment and auto-provisions nodes to execute the jobs. This can be time consuming, taking on the order of 5mins to complete before any work is done. The following sections will describe what happens in these steps and how to diagnose and speed up these interactions. ## Compute cluster creation To execute a Geneva job, you'll need to initialize a compute environment. Here's the basic steps Geneva takes to instantiate that cluster: * User requests a cluster * Scan workspace's python path for modules * Generate local workspace directory zip * Generate python site-packages directory zip(s) * Generate other dirs zip (may include your .venv) * Upload zips * Provision head node * Initialize head node The requests to create an environment can take 5-10 mins to initiate. The most time-consuming steps are generating directory zips and uploading them. AI workloads often require many module packages and can be dependent on specific versions to work. Common modules required for GPU use to run model inferrence can easily be 5GB-10GB of compressed content. On GCE for example, this can take \~5mins to zip all this and \~1min to upload all of this data. To speed this up, Geneva employs caching to help optimize the startup time. There are a few things you can do to make subsequent runs faster, often times \<1 minute: ### Hashing and Caching Geneva generates a hash of each path in the python path that takes into account files and their last modified time. After the first time a directory zip is created and uploaded, the cached copy is used and no new zip is generated or uploaded. However, if there are any changes (e.g. new module added or upgraded) a new hash created and the environment's content is zipped and uploaded. ### Isolate dynamic code and modules If you use a Jupyter notebook environment for your driver, the content of the `.ipynb` file is constantly changing. This means the hash for the directory that contains the notebook will change, even if the subdirectories do not. If your notebook is in your home directory, this could pull in large amounts unneeded code and data. To avoid this you can move your notebook into a subdirectory with no children. When your notebook is executed it is updated but only the notebook content is resent. Other path directories are unchanged, have the same hash and can skip zip and ship. ### Package dependecies into a docker image Geneva has an option to skip the zip and ship of the site-packages. Enabling this assumes that the default docker image is overriden with a custom image that has the `site-package` content preloaded. ### Pre-provision nodes and pods: In your kubernetes configuration, you can tag specific nodes with `geneva.lancedb.com/ray-head` k8s label. These nodes should be configured to be on non-spot instances that are always up. This makes initial kuberay cluster creation quick. ## Job execution A backfill or materialized view jobs triggers the provisioning of worker nodes that will perform the computations and writes. A cold start can be slow because several steps must take place before the UDFs can be applied. However, once nodes and pods are warmed up, the time between submission and execution can be quick. Here's the basic steps Geneva takes to kick off a Geneva job: * User submits job (backfill) * plan scans * provision worker nodes (vms) * load vm * Autoscale workers nodes * provision worker nodes (vms) * load vm * schedule ray actors * download docker images * download zips * execute udf * orchestrate fragment write. In practice, planning the initial distributed step scans require loading vm and pod images. With a cold start, this can take \~5 minutes. Here are some steps you can take to pre-warming worker nodes and pods so that exectuion can be more interactive: **Set worker spec's replicas or min\_replicas to a value >0:** When the kuberay cluster is instantiated this also pre-provision vm's so they are ready for k8s to place pod. replicas (for initial # of worker nodes), and minWorkers (to keep a pool for nodes always up) **Make a warmup call:** Making an initial request to ray will load the pod and zips content to the worker node so that subsequent startups will be fast. **Prevent nodes from auto-scaling down:** During cluster creation, you can specify `idle_timeout_seconds` option -- this is the amount of time before a node needs to be idle before it is considered for de-provisioning. # Troubleshooting Geneva Jobs Source: https://docs.lancedb.com/geneva/jobs/troubleshooting Diagnose and fix common issues when running Geneva backfills, materialized views, and other distributed jobs. This page covers common problems you may hit while running Geneva jobs and how to resolve them. For deployment and cluster issues, see [Troubleshooting Geneva Deployments](/geneva/deployment/troubleshooting). ## Admission control and resource errors Geneva runs **admission control** before starting a job to check that the cluster has enough resources. If the check fails, the job is rejected with `ResourcesUnavailableError` (or a warning if admission is not strict). ### "Job requires GPUs but cluster has no GPU worker groups configured" Your UDF requests GPUs but the cluster has no GPU nodes. **Check your UDF:** ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} @geneva.udf(num_gpus=1) # This requires GPUs def my_udf(x): ... ``` **Fix options:** 1. Remove the GPU requirement if the UDF can run on CPU: `@geneva.udf(num_gpus=0)` (or omit `num_gpus`). 2. Make sure you have a GPU worker group; e.g. in your ClusterBuilder: ``` KubeRayClusterBuilder.create() ... .add_worker_group( KubeRayClusterBuilder.gpu_worker(1) .image(get_ray_image(ray.__version__, _py, gpu=True)) .service_account("geneva-service-account") .node_selector({"geneva.lancedb.com/ray-worker-gpu": "true"}) .build() ) ``` 3. Add GPU worker nodes to your cluster and ensure the cluster definition includes a GPU worker group with the correct node selector (e.g. `"geneva.lancedb.com/ray-worker-gpu": "true"`). ### "UDF requires X CPUs + Y GPUs but no worker group can satisfy all requirements." Of course, if your cluster doesn't have enough GPUs, add them! But if you're surprised how many CPUs/GPUs your UDF is requesting, it may be because of concurrency. Your tasks's actual CPU/GPU requirement is `concurrency × CPUs/GPUs per task`. **Fix:** Lower `concurrency` so that the total CPUs/GPUs needed (concurrency × CPUs/GPUs per task) does not exceed the cluster's CPU/GPU count. ```python theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Before: backfill with `num_gpus(1)` needs 8 GPUs table.backfill("col", concurrency=8) # After: cap at 4 GPUs table.backfill("col", concurrency=4) ``` ### "No single node can satisfy all requirements" The UDF asks for a combination of CPU, memory, and GPU that no single node in the cluster has. This often happens on heterogeneous clusters. **Example:** Node A has 8 CPUs and 4 GB memory; Node B has 4 CPUs and 8 GB memory. A UDF that needs 8 CPUs and 8 GB cannot be placed on either node. **Fix options:** 1. **Reduce UDF resource requests** so they fit on your smallest target node (e.g. lower `num_cpus` or memory). 2. **Add larger nodes** that can satisfy CPU, memory, and GPU together. 3. **Use a homogeneous cluster** where nodes have the same shape. ### Job passes admission but hangs at low progress If admission control passes but the job stalls at a low percentage: **1. Ray dashboard – see what’s actually running** * **Local Ray:** After starting a local cluster, Ray usually prints the dashboard URL (e.g. `http://127.0.0.1:8265`). Open that in a browser. If you didn’t capture it, the dashboard is typically on port **8265** on the host where Ray was started. * **KubeRay (Kubernetes):** Port-forward the dashboard from the Ray head pod, then open it locally: ```bash theme={"theme":{"light":"vitesse-light","dark":"catppuccin-mocha"}} # Find the Ray head pod (replace NAMESPACE and cluster name as needed) kubectl get pods -n NAMESPACE -l ray.io/node-type=head # Forward the dashboard port (Ray uses 8265 for the dashboard) kubectl port-forward -n NAMESPACE POD_NAME 8265:8265 ``` Then open **[http://localhost:8265](http://localhost:8265)** in your browser. * **External Ray cluster:** Use the dashboard URL your cluster operator provides (often the head node’s port 8265). **What to look for in the dashboard:** * **Actors** (or **State** → **Actors**): A long list of actors stuck in **PENDING** means Ray cannot place them (e.g. not enough CPUs, GPUs, or memory on any node). That matches “admission passed but nothing runs.” * **Tasks / Jobs:** If your Geneva job shows as a Ray job, open it and check for tasks that stay **PENDING** or **RUNNING** for a long time without finishing. Pending tasks often mean insufficient resources or that workers aren’t joining. * **Cluster / Nodes:** Check that worker nodes are **ALIVE** and that **Available** resources (CPU, memory, GPU) are not zero. Dead or overloaded nodes can cause jobs to hang. * **Unprovisioned GPUs:** Sometimes the CSP doesn't have enough GPUs available, so even though you have correctly requested them, the job may be unable to run. In this case, run on CPU if possible, or try your job again later. **2. Memory pressure** If nodes are full or workers are being OOM-killed, the job can stall or fail. In the Ray dashboard, check node memory usage; in Kubernetes, check pod restarts (`kubectl get pods -n NAMESPACE`) and pod logs for OOM. Also try reducing `concurrency` or the UDF’s memory request, even slightly. **3. Writers / queues** Geneva uses Ray actors for writers and queues. If the job is stuck at a fixed percentage, writers may be blocked (e.g. on storage or version conflicts). Check Ray dashboard **Actors** for writer-like actors that stay **RUNNING** but make no progress, and check cluster or pod logs for errors from Geneva (e.g. commit or connection errors). You can relax or skip admission to narrow down whether the failure is admission vs. scheduling: set `GENEVA_ADMISSION__CHECK=false` or use `_admission_check=False` on the backfill (for testing only). See [Advanced configuration](/geneva/udfs/advanced-configuration/) for more details. *** ## Ray connection and startup ### "Geneva was unable to connect to the Ray head" The client could not connect to the Ray cluster (e.g. after starting a KubeRay or external cluster). **Common causes:** * **Head not ready** – The Ray head pod may still be starting. Wait a bit and retry, or increase `GENEVA_RAY_INIT_MAX_RETRIES` (default 5). * **Image/architecture mismatch** – The Ray head image may not match the node architecture (e.g. arm64 vs x64). Use an image built for the same architecture as your nodes. (the function [`get_ray_image`](https://lancedb.github.io/geneva/api/utils/) can help find the right image name.) * **Network / firewall** – If using an external Ray cluster, ensure the `ray://` address is reachable and that no firewall is blocking the Ray client port. **Quick check:** From the same network as the client, try connecting with `ray.init("ray://:")` in a small script to see the exact error. ### Ray client "already connected" or init fails on retry If you see errors about the client already being connected (e.g. when re-running a notebook or script), ensure you're not holding an old Ray client connection. Restart the kernel or process so Ray is re-initialized cleanly. Geneva disconnects the client when the context exits; leaving a context open or reusing a stale connection can cause this. ### Head node out of memory If the head node is under-provisioned (e.g. 1 CPU / 2 GB), it can OOM when: * Many workers connect and register with GCS * The Ray dashboard accumulates metrics * Object store spillover occurs **Recommendation**: Use at least 4 CPU / 8 GiB for the head node in any non-trivial deployment. This is the current default. *** ## Serialization library or `attrs` version mismatch Ray and Geneva use cloudpickle and can be sensitive to library versions. If you see `TypeError: Enum.__new__() missing 1 required positional argument: 'value'` or similar pickling errors with no obvious non-serializable object, ensure **client and cluster use the same Python minor version and compatible library versions** (e.g. same `attrs`). Run the job from a machine with the same OS/architecture as the workers when possible so that shipped environments match. *** ## Permissions and storage ### GCS / S3 permission denied in job logs Workers run under a Kubernetes service account (and possibly a cloud IAM role). If you see `PermissionError`, `storage.objects.get` denied, or 403 from object storage: 1. **Service account** – Confirm the Geneva Ray head and worker specs use the intended `service_account`. That account must have read/write to the bucket (e.g. GCS `roles/storage.objectUser` or equivalent S3 permissions). 2. **Workload identity** – On GKE, bind the K8s service account to a Google service account with bucket access. On EKS, use IRSA or node IAM so the pod role can access the bucket. See [Troubleshooting Geneva Deployments](/geneva/deployment/troubleshooting) for permission examples and service account configuration. *** ## Version conflicts and commit retries ### Version conflicts during commit Concurrent backfills or other writers can cause version conflicts when committing. Geneva retries with merging; if conflicts persist, you may see repeated retries or failures. **Options:** * Reduce concurrency or avoid overlapping backfills to the same table/fragments. * Tune retries (e.g. `GENEVA_VERSION_CONFLICT_MAX_RETRIES`, default 10) if you expect transient contention. See [Advanced configuration](/geneva/udfs/advanced-configuration/). ### Writer stalls or commit timeouts If writers are slow (e.g. under resource contention), they may be considered stalled and restarted. You can increase the idle tolerance with `GENEVA_WRITER_STALL_IDLE_ROUNDS` (default 6 rounds of 5s). For commit timeouts or transient storage errors, `GENEVA_COMMIT_MAX_RETRIES` (default 12) controls how many times Geneva retries the commit. *** ## Materialized views ### Matview refresh fails with resource errors Refreshing a materialized view runs admission control for each UDF in the view. If any UDF’s resource requirements cannot be satisfied, the refresh fails. Ensure the cluster has enough resources for **all** UDFs used in the view (same as for a single backfill), and that no single UDF asks for more than any one node can provide. *** ## Quick checks before running a job * **Versions** – Same Ray version on client and cluster; same Python minor (e.g. 3.10.x) on both. See [Troubleshooting Geneva Deployments](/geneva/deployment/troubleshooting#confirming-dependency-versions). * **Remote execution** – Use `ray.available_resources()` and a simple `@ray.remote` task to confirm the cluster is reachable and has the expected CPUs/GPUs/memory. * **Permissions** – Run a minimal remote task that `import geneva` and touches the same bucket/path as your job to surface permission issues early. ## API Reference * [UDF](https://lancedb.github.io/geneva/api/udf/) — `@udf` decorator options: `num_gpus`, `num_cpus`, `memory` * [Cluster](https://lancedb.github.io/geneva/api/cluster/) — `KubeRayClusterBuilder`, `CpuWorkerBuilder`, `GpuWorkerBuilder` * [Table](https://lancedb.github.io/geneva/api/table/) — `backfill()`, `refresh()` * [Error Handling](https://lancedb.github.io/geneva/api/error_handling/) — `FatalWorkerOOMError`, `FatalWorkerCrashError`, and other worker error types # What is Feature Engineering? Source: https://docs.lancedb.com/geneva/overview/index Learn how to transform raw data into meaningful features for AI models using LanceDB's feature engineering capabilities. Scale your feature engineering workflows with distributed processing and UDFs. This section introduces the concept of feature engineering using an example of a product recommendation system.