Lance
Lance
Lance는 AI를 위한 오픈 멀티모달 레이크하우스 테이블 형식이에요. Hugging Face 경로(hf://)로 Hub의 Lance 데이터셋에 접근할 수 있어요. 이렇게 하면 전체 데이터셋을 로컬로 복사하지 않고도 Hugging Face Hub의 큰 데이터셋을 스캔하고 검색할 수 있어요.
출처: 문서
본문
시작하기
시작하려면 pylance와 pyarrow를 pip install 하세요.
pip install pylance pyarrow
왜 Lance인가?
- ML/AI 워크로드에 최적화: Lance는 스캔 성능을 희생하지 않으면서 빠른 랜덤 접근을 위해 설계된 현대적 컬럼형 형식으로, 검색, 분석, 학습, 피처 엔지니어링과 많은 사용 사례에 유용해요.
- 멀티모달 자산은 바이트 또는 바이너리 객체("blobs as files")로 Lance 안에 임베딩 및 전통적 스칼라 데이터와 함께 저장돼요. 그래서 Hub를 통해 큰 데이터셋을 더 쉽게 관리·공유·배포할 수 있어요.
- 인덱싱이 일급 시민(형식 자체에 내장): Lance에는 빠르고 온디스크이며 확장 가능한 벡터 및 FTS 인덱스가 Hub의 데이터셋과 나란히 있으므로, 데이터뿐 아니라 임베딩과 인덱스도 공유할 수 있어 사용자가 다시 계산할 필요가 없어요.
- 유연한 스키마와 데이터 진화(data evolution)로 전체 테이블을 다시 쓰지 않고도 새 피처/컬럼(모더레이션 태그, 임베딩 등)을 점진적으로 추가할 수 있어요.
모든 데이터를 한 곳에 저장
Lance에서 멀티모달 데이터 자산(이미지, 오디오, 비디오)은 원시 바이트로 스칼라 메타데이터와 임베딩과 함께 저장돼요. 이렇게 하면 여러 스토리지 시스템을 연결할 필요 없이 한 곳에서 데이터셋을 쉽게 스캔하고 필터링할 수 있어요.
datasets로 Hub에서 스트리밍
load_dataset(..., streaming=True)를 사용해 로컬로 다운로드하지 않고 데이터를 스캔·순회할 수 있어요.
from datasets import load_dataset
# Return as a Hugging Face dataset
ds = load_dataset(
"lance-format/laion-1m",
split="train",
streaming=True
)
# Take first three rows
for row in ds.take(3):
print(row["caption"])
스트리밍은 무엇이 있는지 이해하기 위해 메타데이터를 샘플링하는 데 아주 좋아요. 벡터 검색이나 큰 바이너리 blob 작업에는 아래 설명하는 Lance dataset API를 사용할 수 있어요.
[!WARNING] 스트리밍은 단순한 스칼라 메타데이터 샘플링에는 빠르지만 임베딩이나 큰 멀티모달 자산에는 그리 빠르지 않아요. 큰 데이터셋을 작업할 때는 메타데이터를 스캔하고 필요한 부분집합을 식별한 뒤 그 부분만 로컬로 다운로드해 Hub 속도 제한을 피하는 걸 권장해요.
hf download lance-format/laion-1m --repo-type dataset --local-dir ./laion
lance.dataset로 Hub에서 스트리밍
hf:// 경로 지정자로 Hugging Face Hub에 저장된 Lance 데이터셋을 스캔할 수도 있어요. 이는 로컬 다운로드 없이 원격 데이터셋을 스캔해요. Lance dataset API로 리밋·필터·프로젝션을 설정해 필요한 데이터만 가져오기 매우 간단해요.
import lance
# Return as a Lance dataset
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
scanner = ds.scanner(
columns=["caption", "url", "similarity"],
limit=5
)
rows = scanner.to_table().to_pylist()
for row in rows:
print(row)
바이너리 자산 작업
아래 예시는 image 컬럼의 원시 JPEG 바이트로 Lance 데이터셋에서 이미지를 가져와 후속 작업에 사용하는 방법을 보여줘요. ds.take로 바이트를 가져와 디스크에 써서 다른 곳에서 사용할 수 있어요.
import lance
from pathlib import Path
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
dir_name = "laion_samples"
Path(dir_name).mkdir(exist_ok=True)
rows = ds.take([0, 1], columns=["image", "caption"]).to_pylist()
for idx, row in enumerate(rows):
with open(f"{dir_name}/{idx}.jpg", "wb") as f:
f.write(row["image"])
print(f"Wrote image with caption: {row['caption']}")
부분집합을 새 Lance 데이터셋으로 쓰기
큰 데이터셋을 다루나요? Hub에서 필터된 스캔을 실행해 행 부분집합을 선택하고 로컬 Lance 데이터셋으로 구체화하는 것이 간단해요.
import lance
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
scanner = ds.scanner(
columns=["image", "caption", "width", "height"],
filter="width >= 200 AND height >= 100",
limit=10,
)
subset = scanner.to_table()
lance.write_dataset(subset, "./laion_subset")
인덱스 만들기
데이터셋에 연결된 인덱스가 없다면 로컬로 다운로드한 뒤 만들 수 있어요.
# ds is a local Lance dataset
ds.create_index(
"img_emb",
index_type="IVF_PQ",
num_partitions=256,
num_sub_vectors=96,
replace=True,
)
벡터 인덱스 생성에 대한 더 자세한 예시는 Lance 문서를 참고하세요. 벡터 인덱스를 만들면 임베딩으로 데이터에 유사도 검색을 실행할 수 있어요.
벡터 검색
Lance에서 인덱스는 일급 시민이므로, 데이터뿐 아니라 임베딩과 인덱스도 함께 저장하고 Hub에서 직접 쿼리할 수 있어요. describe_indices() 메서드로 데이터셋의 인덱스 정보를 나열하면 돼요. 데이터셋에 인덱스가 없으면 lance.write_dataset()으로 로컬 버전을 쓰고 LanceDataset.create_index로 필요한 인덱스를 만들 수 있어요.
아래 예시는 img_emb 필드에 이미 벡터 인덱스가 있는 데이터셋을 보여줘요.
import lance
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
print(ds.list_indices())
# Returns
# [
# IndexDescription(
# name=img_emb_idx,
# type_url=/lance.table.VectorIndexDetails,
# num_rows_indexed=1209588,
# fields=[15],
# field_names=["img_emb"],
# num_segments=1
#. )
# ]
원격 데이터셋에서 다운로드하지 않고 바로 벡터 검색 쿼리를 실행할 수 있어요(또는 원하면 로컬로 다운로드해 새 인덱스를 만들 수 있어요). 아래 예시는 이미지 임베딩을 쿼리 벡터로 사용해 벡터 인덱스에서 최근접 이웃 검색을 실행하는 방법을 보여줘요.
import lance
import pyarrow as pa
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
emb_field = ds.schema.field("img_emb")
ref = ds.take([0], columns=["img_emb"]).to_pylist()[0]["img_emb"]
query = pa.array([ref], type=emb_field.type)
neighbors = ds.scanner(
nearest={
"column": emb_field.name,
"q": query[0],
"k": 6,
"nprobes": 16,
"refine_factor": 30,
},
columns=["caption", "url", "similarity"],
).to_table().to_pylist()
[!NOTE] 큰
k나nprobes값을 설정하거나 큰 쿼리 배치를 한 번에 보내면 Hub 속도 제한에 걸릴 수 있어요. 과도한 사용 시에는 데이터셋(또는 부분집합)을 로컬로 다운로드하고 Lance가 로컬 경로를 가리키게 해 조절(throttling)을 피하세요.
데이터 진화
Lance의 가장 강력한 기능 중 하나는 유연하고 비용이 0인 데이터 진화예요. 즉 원본 테이블을 다시 쓰지 않고 파생 컬럼을 손쉽게 추가할 수 있다는 뜻이에요. 큰 blob이 많은 매우 큰 테이블에서는 I/O 절감이 상당할 수 있어요. 이 기능은 ML/AI 엔지니어링 작업을 위해 데이터를 실험하며 새 피처·임베딩·파생 메타데이터를 자주 추가하는 경우 특히 관련이 깊어요.
아래 예시는 기존 score 컬럼을 기반으로 이미지를 NSFW로 표시하는 파생 moderation_label 컬럼을 추가하는 방법을 보여줘요. 이 변경을 할 때 새 컬럼을 백필(backfill)하는 것은 새 컬럼 데이터만 쓰고 원본 이미지 blob이나 다른 컬럼의 데이터는 건드리지 않아요. 백필 없이 새 컬럼 스키마만 추가할 수도 있어요.
import lance
import pyarrow as pa
# Assumes you ran the export to Lance example above to store a local subset of the data
local_ds = lance.dataset("./laion_subset")
# schema only (data to be added later)
local_ds.add_columns(pa.field("moderation_label", pa.string()))
# with data backfill
local_ds.add_columns(
{
"moderation_label": "case WHEN \"NSFW\" > 0.5 THEN 'review' ELSE 'ok' END"
}
)
Lance 데이터셋에서 컬럼을 변경·삭제하는 방법은 Lance 데이터 진화 문서를 참고하세요.
비디오 blob 작업
Lance 테이블은 큰 인라인 비디오 blob도 지원해요. 이 논문의 OpenVid-1M 데이터셋에는 고품질이고 표현력 있는 비디오와 자막이 들어 있어요. 비디오 데이터는 Hub의 다음 Lance 데이터셋 video_blob 컬럼에 저장돼요.
import lance
lance_ds = lance.dataset("hf://datasets/lance-format/Openvid-1M/data/train.lance")
blob_file = lance_ds.take_blobs("video_blob", ids=[0])[0]
video_bytes = blob_file.read()
다른 데이터 형식과 달리 큰 멀티모달 바이너리 객체(blob)는 Lance에서 일급 시민이에요. blob API는 Lance 데이터셋에서 큰 blob을 저장·검색하는 고수준 API를 제공해요. 아래 예시는 무거운 비디오 blob을 로드하지 않고 메타데이터를 효율적으로 탐색한 다음, 필요할 때 관련 비디오 blob을 가져오는 방법을 보여줘요.
import lance
ds = lance.dataset("hf://datasets/lance-format/Openvid-1M/data/train.lance")
# 1. Browse metadata without loading video blobs.
metadata = ds.scanner(
columns=["caption", "aesthetic_score"],
filter="aesthetic_score >= 4.5",
limit=2,
).to_table().to_pylist()
# 2. Fetch a single video blob by row index.
selected_index = 0
blob_file = ds.take_blobs("video_blob", ids=[selected_index])[0]
with open("video_0.mp4", "wb") as f:
f.write(blob_file.read())
학습용 데이터 준비
학습도 Lance의 빠른 랜덤 접근과 스캔 성능이 유용한 영역이에요. Lance 데이터셋을 학습 데이터 저장 메커니즘으로 사용하고, 학습 파이프라인의 일부로 셔플하고 배치로 로드할 수 있어요.
Lance의 blob API는 torchcodec와 호환되므로 비디오 blob을 torch 텐서로 쉽게 디코딩할 수 있어요.
from torchcodec.decoders import VideoDecoder
decoder = VideoDecoder(blob_file)
tensor = decoder[0] # uint8 tensor of shape [C, H, W]
비디오를 효율적으로 디코딩하는 더 많은 함수는 torchcodec 문서를 참고하세요.
또한 Lance 문서에서 이미지 데이터를 torchvision으로 로드해 직접 이미지 모델을 학습하는 예시를 확인할 수 있어요.
더 많은 Lance 데이터셋 탐색
Lance는 전통적인 테이블형 데이터와 함께 멀티모달 blob을 네이티브 지원하는 오픈 형식이에요. Hugging Face Hub 통합 덕분에 이미지, 오디오, 비디오, 텍스트, 임베딩, 스칼라 메타데이터를 한 곳에서 쉽게 작업할 수 있어요.
Hugging Face Hub에서 더 많은 Lance 데이터셋을 탐색하고, 직접 만든 Lance 데이터셋을 커뮤니티와 공유하세요! 더 많은 코드 스니펫과 예시는 lance.org에서 확인할 수 있어요.
더 알아보기 (Learn more)
Lance는 멀티모달 blob과 벡터 인덱스를 네이티브 지원하는 컬럼형 형식이에요. hf:// 경로로 원격 데이터셋을 다운로드 없이 스캔·검색할 수 있고, datasets 스트리밍이나 lance.dataset API를 사용해요. 벡터 인덱스가 일급이므로 Hub에서 바로 유사도 검색이 가능하고, 데이터 진화로 원본을 다시 쓰지 않고 컬럼을 추가할 수 있어요.