Hub documentation

Dask

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Dask

Dask๋Š” ๊ธฐ์กด Python ๋ฐ PyData ์ƒํƒœ๊ณ„๋ฅผ ํ™•์žฅํ•˜๋Š” ๋ณ‘๋ ฌ ๋ฐ ๋ถ„์‚ฐ ์ปดํ“จํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

ํŠนํžˆ Dask DataFrame๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด pandas ์›Œํฌํ”Œ๋กœ๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Dask DataFrame์€ pandas๋ฅผ ๋ณ‘๋ ฌํ™”ํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ํ‘œ ํ˜•์‹ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. pandas API๋ฅผ ๋ฐ€์ ‘ํ•˜๊ฒŒ ๋ฐ˜์˜ํ•˜๋ฏ€๋กœ, ๋‹จ์ผ ๋ฐ์ดํ„ฐ์…‹์—์„œ ํ…Œ์ŠคํŠธํ•˜๋˜ ๊ฒƒ์„ ์ „์ฒด ๋ฐ์ดํ„ฐ์…‹ ์ฒ˜๋ฆฌ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. Dask๋Š” Hugging Face Datasets์˜ ๊ธฐ๋ณธ ํ˜•์‹์ธ Parquet์™€ ํŠนํžˆ ํšจ๊ณผ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋ฉฐ, ํ’๋ถ€ํ•œ ๋ฐ์ดํ„ฐ ์œ ํ˜•, ํšจ์œจ์ ์ธ ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง, ์••์ถ•์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Dask์˜ ์ข‹์€ ์‹ค์šฉ์  ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” ๋ฐ์ดํ„ฐ์…‹์— ๋Œ€ํ•ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋˜๋Š” ๋ชจ๋ธ ์ถ”๋ก ์„ ๋ถ„์‚ฐ ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Coiled์˜ Hugging Face + Dask๋ฅผ ํ™œ์šฉํ•œ AI ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ™•์žฅ์— ๋Œ€ํ•œ ํ›Œ๋ฅญํ•œ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์ฝ๊ธฐ ๋ฐ ์“ฐ๊ธฐ

Dask์€ ์›๊ฒฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ฐ๊ธฐ ์œ„ํ•ด fsspec๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, Hugging Face ๊ฒฝ๋กœ(hf://)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Hub์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ € Hugging Face ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

hf auth login

๊ทธ ํ›„ ๋ฐ์ดํ„ฐ์…‹ ์ €์žฅ์†Œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

from huggingface_hub import HfApi

HfApi().create_repo(repo_id="username/my_dataset", repo_type="dataset")

๋งˆ์ง€๋ง‰์œผ๋กœ, Dask์—์„œ Hugging Face ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Dask DataFrame์€ Hugging Face์— Parquet์„ ๋ถ„์‚ฐ์œผ๋กœ ๊ธฐ๋กํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•˜๋ฉฐ, ์ด๋Š” ์ปค๋ฐ‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์…‹ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค:

import dask.dataframe as dd

df.to_parquet("hf://datasets/username/my_dataset")

# or write in separate directories if the dataset has train/validation/test splits
df_train.to_parquet("hf://datasets/username/my_dataset/train")
df_valid.to_parquet("hf://datasets/username/my_dataset/validation")
df_test .to_parquet("hf://datasets/username/my_dataset/test")

์ด ๋ฐฉ์‹์€ ํŒŒ์ผ๋ณ„๋กœ ์ปค๋ฐ‹์„ ํ•˜๋‚˜์”ฉ ์ƒ์„ฑํ•˜๋ฏ€๋กœ, ์—…๋กœ๋“œ ํ›„ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์Šค์ฟผ์‹œํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค:

from huggingface_hub import HfApi

HfApi().super_squash_history(repo_id=repo_id, repo_type="dataset")

์ด ์ž‘์—…์€ Parquet ํ˜•์‹์˜ Dask ๋ฐ์ดํ„ฐ์…‹์„ ํฌํ•จํ•˜๋Š” ๋ฐ์ดํ„ฐ์…‹ ์ €์žฅ์†Œ username/my_dataset๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๋‹ค์‹œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import dask.dataframe as dd

df = dd.read_parquet("hf://datasets/username/my_dataset")

# or read from separate directories if the dataset has train/validation/test splits
df_train = dd.read_parquet("hf://datasets/username/my_dataset/train")
df_valid = dd.read_parquet("hf://datasets/username/my_dataset/validation")
df_test  = dd.read_parquet("hf://datasets/username/my_dataset/test")

Hugging Face ๊ฒฝ๋กœ ๋ฐ ๊ตฌํ˜„ ๋ฐฉ์‹์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ HfFileSystem์— ๋Œ€ํ•œ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

Dask๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์…‹์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด, ๋จผ์ € pandas DataFrame ๋˜๋Š” Series์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•œ ๋‹ค์Œ, Dask์˜ map_partitions ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ํ•จ์ˆ˜๋ฅผ ๋ฐ์ดํ„ฐ์…‹์˜ ๋ชจ๋“  ํŒŒํ‹ฐ์…˜์— ๋ณ‘๋ ฌ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

def dummy_count_words(texts):
    return pd.Series([len(text.split(" ")) for text in texts])

๋˜๋Š” pandas ๋ฌธ์ž์—ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์œ ์‚ฌํ•œ ํ•จ์ˆ˜(๋” ๋น ๋ฆ„):

def dummy_count_words(texts):
    return texts.str.count(" ")

pandas์—์„œ๋Š” ์ด ํ•จ์ˆ˜๋ฅผ ํ…์ŠคํŠธ ์—ด์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

# pandas API
df["num_words"] = dummy_count_words(df.text)

๊ทธ๋ฆฌ๊ณ  Dask์—์„œ๋Š” ์ด ํ•จ์ˆ˜๋ฅผ ๋ชจ๋“  ํŒŒํ‹ฐ์…˜์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

# Dask API: run the function on every partition
df["num_words"] = df.text.map_partitions(dummy_count_words, meta=int)

ํ•จ์ˆ˜์˜ ์ถœ๋ ฅ์ธ pandas Series ๋˜๋Š” DataFrame์˜ ์œ ํ˜•์ธ meta๋„ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์ด๋Š” Dask DataFrame์ด ์ง€์—ฐ ํ‰๊ฐ€(lazy) API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Dask๋Š” .compute()๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฏ€๋กœ, ๊ทธ ์‚ฌ์ด์— ์ƒˆ๋กœ์šด ์—ด์˜ ์œ ํ˜•์„ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด meta ์ธ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Predicate and Projection Pushdown

Hugging Face์—์„œ Parquet ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ, Dask๋Š” Parquet ํŒŒ์ผ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ํ™œ์šฉํ•˜์—ฌ ํ•„์š”ํ•˜์ง€ ์•Š์€ ํŒŒ์ผ์ด๋‚˜ row group ์ „์ฒด๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Parquet ํ˜•์‹์˜ Hugging Face Dataset์— ํ•„ํ„ฐ(predicate)๋ฅผ ์ ์šฉํ•˜๊ฑฐ๋‚˜ ์ปฌ๋Ÿผ์˜ ๋ถ€๋ถ„ ์ง‘ํ•ฉ(projection)์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ, Dask๋Š” Parquet ํŒŒ์ผ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์„ ๋‹ค์šด๋กœ๋“œํ•˜์ง€ ์•Š๊ณ  ํ๊ธฐํ•ฉ๋‹ˆ๋‹ค.

Dask DataFrame API์˜ ์žฌ๊ตฌํ˜„์„ ํ†ตํ•ด ์ฟผ๋ฆฌ ์ตœ์ ํ™”๋ฅผ ์ง€์›ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด Dask๊ฐ€ ๋” ๋น ๋ฅด๊ณ  ๊ฒฌ๊ณ ํ•ด์กŒ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด FineWeb-Edu์˜ ์ด ํ•˜์œ„ ์ง‘ํ•ฉ์—๋Š” ๋งŽ์€ Parquet ํŒŒ์ผ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ตœ๊ทผ CC ๋คํ”„์˜ ํ…์ŠคํŠธ๋งŒ ์œ ์ง€ํ•˜๋„๋ก ๋ฐ์ดํ„ฐ์…‹์„ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, Dask๋Š” ๋Œ€๋ถ€๋ถ„์˜ ํŒŒ์ผ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ํ•„ํ„ฐ์— ์ผ์น˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค:

import dask.dataframe as dd

df = dd.read_parquet("hf://datasets/HuggingFaceFW/fineweb-edu/sample/10BT/*.parquet")

# Dask will skip the files or row groups that don't
# match the query without downloading them.
df = df[df.dump >= "CC-MAIN-2023"]

Dask๋Š” ๊ณ„์‚ฐ์— ํ•„์š”ํ•œ ์—ด๋งŒ ์ฝ๊ณ  ๋‚˜๋จธ์ง€๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ฝ”๋“œ ํ›„๋ฐ˜๋ถ€์— ํŠน์ • ์—ด์„ ์ œ๊ฑฐํ•˜๋”๋ผ๋„ ํŒŒ์ดํ”„๋ผ์ธ ์ดˆ๊ธฐ์— ํ•ด๋‹น ์—ด์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋กœ๋“œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์—ด์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์„ ์กฐ์ž‘ํ•˜๊ฑฐ๋‚˜ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

# Dask will download the 'dump' and 'token_count' needed
# for the filtering and computation and skip the other columns.
df.token_count.mean().compute()

ํด๋ผ์ด์–ธํŠธ

dask์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์€ ํด๋Ÿฌ์Šคํ„ฐ ๋˜๋Š” ๋กœ์ปฌ Client๋ฅผ ํ†ตํ•ด ๋ณ‘๋ ฌ ๊ณ„์‚ฐ์„ ์‹œ์ž‘ํ•˜๋„๋ก ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

import dask.dataframe as dd
from distributed import Client

if __name__ == "__main__":  # needed for creating new processes
    client = Client()
    df = dd.read_parquet(...)
    ...

๋กœ์ปฌ ์‚ฌ์šฉ์˜ ๊ฒฝ์šฐ, Client๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ์„ ์‚ฌ์šฉํ•˜๋Š” Dask LocalCluster๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. LocalCluster์˜ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜๋™์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from dask.distributed import Client, LocalCluster
cluster = LocalCluster(n_workers=8, threads_per_worker=8)
client = Client(cluster)

Client ์—†์ด ๋กœ์ปฌ์—์„œ ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ํŠน์ • ์ž‘์—… ์ดํ›„์— DataFrame์ด ๋” ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐ).

๋กœ์ปฌ ๋˜๋Š” ํด๋ผ์šฐ๋“œ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Dask ๋ฐฐํฌ ๋ฌธ์„œ์—์„œ ํ™•์ธํ•˜์„ธ์š”.

Update on GitHub