Phân đoạn nội dung dựa trên Parquet

  • 11 min read
Phân đoạn nội dung dựa trên Parquet

Phân đoạn Nội dung Định nghĩa trong Parquet

Giảm thời gian tải lên và tải xuống tệp Parquet trên Hugging Face Hub bằng cách tận dụng lớp lưu trữ Xet mới và tính năng Phân đoạn Nội dung Định nghĩa (CDC) của Apache Arrow, cho phép quy trình làm việc dữ liệu hiệu quả và có khả năng mở rộng hơn.

TL;DR: Phân đoạn Nội dung Định nghĩa (CDC) trong Parquet hiện đã có sẵn trong PyArrow và Pandas, cho phép loại bỏ trùng lặp hiệu quả các tệp Parquet trên các hệ thống lưu trữ có thể định địa chỉ nội dung như lớp lưu trữ Xet của Hugging Face. CDC giảm đáng kể chi phí lưu trữ và truyền dữ liệu bằng cách chỉ tải lên hoặc tải xuống các phân đoạn dữ liệu đã thay đổi. Bật CDC bằng cách truyền đối số use_content_defined_chunking:

import pandas as pd
import pyarrow.parquet as pq

df.to_parquet("hf://datasets/{user}/{repo}/path.parquet", use_content_defined_chunking=True)
pq.write_table(table, "hf://datasets/{user}/{repo}/path.parquet", use_content_defined_chunking=True)

Mục lục

Giới thiệu

Apache Parquet là một định dạng lưu trữ theo cột được sử dụng rộng rãi trong cộng đồng kỹ thuật dữ liệu.

Tính đến thời điểm hiện tại, Hugging Face lưu trữ gần 21 PB tập dữ liệu, trong đó riêng các tệp Parquet chiếm hơn 4 PB dung lượng lưu trữ đó. Do đó, tối ưu hóa lưu trữ Parquet là một ưu tiên cao. Hugging Face đã giới thiệu một lớp lưu trữ mới gọi là Xet tận dụng phân đoạn nội dung được xác định để loại bỏ hiệu quả các đoạn dữ liệu trùng lặp, giảm chi phí lưu trữ và cải thiện tốc độ tải xuống/tải lên.

Mặc dù Xet không phụ thuộc vào định dạng, nhưng bố cục của Parquet và nén dựa trên phân đoạn cột (trang dữ liệu) có thể tạo ra các biểu diễn cấp byte hoàn toàn khác nhau cho dữ liệu có các thay đổi nhỏ, dẫn đến hiệu suất loại bỏ trùng lặp không tối ưu. Để giải quyết vấn đề này, các tệp Parquet nên được viết theo cách giảm thiểu sự khác biệt ở cấp byte giữa các dữ liệu tương tự, đó là nơi phân đoạn nội dung được xác định (CDC) phát huy tác dụng.

Hãy cùng khám phá những lợi ích về hiệu suất của tính năng Parquet CDC mới được sử dụng cùng với lớp lưu trữ Xet của Hugging Face.

Chuẩn bị dữ liệu

Để minh họa, chúng ta sẽ sử dụng một tập hợp con có kích thước có thể quản lý của tập dữ liệu OpenOrca.

import numpy as np
import pyarrow as pa
import pyarrow.compute as pc
import pyarrow.parquet as pq
from huggingface_hub import hf_hub_download


def shuffle_table(table, seed=40):
    rng = np.random.default_rng(seed)
    indices = rng.permutation(len(table))
    return table.take(indices)


# download the dataset from Hugging Face Hub into local cache
path = hf_hub_download(
    repo_id="Open-Orca/OpenOrca", 
    filename="3_5M-GPT3_5-Augmented.parquet", 
    repo_type="dataset"
)

# read the cached parquet file into a PyArrow table 
orca = pq.read_table(path, schema=pa.schema([
    pa.field("id", pa.string()),
    pa.field("system_prompt", pa.string()),
    pa.field("question", pa.large_string()),
    pa.field("response", pa.large_string()),
]))

# augment the table with some additional columns
orca = orca.add_column(
    orca.schema.get_field_index("question"),
    "question_length",
    pc.utf8_length(orca["question"])
)
orca = orca.add_column(
    orca.schema.get_field_index("response"),
    "response_length",
    pc.utf8_length(orca["response"])
)

# shuffle the table to make it unique to the Xet storage
orca = shuffle_table(orca)

# limit the table to the first 100,000 rows 
table = orca[:100_000]

# take a look at the first 3 rows of the table
table[:3].to_pandas()

Tải bảng lên dưới dạng tệp Parquet lên Hugging Face Hub

Kể từ pyarrow>=21.0.0, chúng ta có thể sử dụng URI Hugging Face trong các hàm pyarrow để trực tiếp đọc và ghi các tệp parquet (và các định dạng tệp khác) vào Hub bằng lược đồ URI hf://.

>>> pq.write_table(table, "hf://datasets/kszucs/pq/orca.parquet")
New Data Upload: 100%|███████████████████████████████████████████████| 96.1MB / 96.1MB, 48.0kB/s  
Total Bytes:  96.1M
Total Transfer:  96.1M

Chúng ta có thể thấy rằng bảng đã được tải lên hoàn toàn (tổng số byte == tổng số chuyển) dưới dạng dữ liệu mới vì nó chưa được biết đến lớp lưu trữ Xet. Bây giờ, hãy đọc lại nó dưới dạng bảng pyarrow:

downloaded_table = pq.read_table("hf://datasets/kszucs/pq/orca.parquet")
assert downloaded_table.equals(table)

Lưu ý rằng tất cả các hàm pyarrow chấp nhận đường dẫn tệp cũng chấp nhận URI Hugging Face, như tập dữ liệu pyarrow, hàm CSV, trình ghi Parquet gia tăng hoặc chỉ đọc siêu dữ liệu parquet:

pq.read_metadata("hf://datasets/kszucs/pq/orca.parquet")
<pyarrow._parquet.FileMetaData object at 0x16ebfa980>
  created_by: parquet-cpp-arrow version 21.0.0-SNAPSHOT
  num_columns: 6
  num_rows: 100000
  num_row_groups: 1
  format_version: 2.6
  serialized_size: 4143

Các trường hợp sử dụng khác nhau để loại bỏ trùng lặp Parquet

Để chứng minh hiệu quả của tính năng phân đoạn nội dung được xác định, chúng ta sẽ thử nghiệm xem nó hoạt động như thế nào trong trường hợp:

  1. Tải lại các bản sao chính xác của bảng
  2. Thêm/xóa các cột khỏi bảng
  3. Thay đổi các kiểu cột trong bảng
  4. Nối thêm các hàng mới và nối các bảng
  5. Chèn / Xóa các hàng trong bảng
  6. Thay đổi kích thước nhóm hàng của bảng
  7. Sử dụng các phân chia cấp tệp khác nhau

1. Tải lại các bản sao chính xác của bảng

Mặc dù trường hợp sử dụng này nghe có vẻ tầm thường, nhưng các hệ thống tệp truyền thống không loại bỏ trùng lặp các tệp, dẫn đến việc tải lại và tải xuống lại toàn bộ dữ liệu. Ngược lại, một hệ thống sử dụng phân đoạn nội dung được xác định có thể nhận ra rằng nội dung tệp là giống hệt nhau và tránh truyền dữ liệu không cần thiết.

>>> pq.write_table(table, "hf://datasets/kszucs/pq/orca-copy.parquet")
New Data Upload: |                                                   | 0.00B / 0.00B, 0.00B/s  
Total Bytes:  96.1M
Total Transfer:  0.00

Chúng ta có thể thấy rằng không có dữ liệu mới nào được tải lên và thao tác diễn ra ngay lập tức. Bây giờ, hãy xem điều gì xảy ra nếu chúng ta tải lên lại cùng một tệp nhưng vào một kho lưu trữ khác:

>>> pq.write_table(table, "hf://datasets/kszucs/pq-copy/orca-copy-again.parquet")
New Data Upload: |                                                   | 0.00B / 0.00B, 0.00B/s  
Total Bytes:  96.1M
Total Transfer:  0.00

Việc tải lên lại diễn ra ngay lập tức vì việc loại bỏ trùng lặp cũng hoạt động trên các kho lưu trữ. Đây là một tính năng quan trọng của lớp lưu trữ Xet, cho phép chia sẻ và cộng tác dữ liệu hiệu quả. Bạn có thể đọc thêm về các chi tiết và thách thức mở rộng quy mô trong bài đăng trên blog Từ Phân đoạn đến Khối: Tăng tốc Tải lên và Tải xuống trên Hub.

2. Thêm và Xóa các cột khỏi bảng

Trước tiên, hãy ghi các bảng gốc và đã thay đổi vào các tệp parquet cục bộ để xem kích thước của chúng:

table_with_new_columns = table.add_column(
    table.schema.get_field_index("response"),
    "response_short",
    pc.utf8_slice_codeunits(table["response"], 0, 10)
)
table_with_removed_columns = table.drop(["response"])
    
pq.write_table(table, "/tmp/original.parquet")
pq.write_table(table_with_new_columns, "/tmp/with-new-columns.parquet")
pq.write_table(table_with_removed_columns, "/tmp/with-removed-columns.parquet")
!ls -lah /tmp/*.parquet
-rw-r--r--  1 kszucs  wheel    92M Jul 22 14:47 /tmp/original.parquet
-rw-r--r--  1 kszucs  wheel    92M Jul 22 14:47 /tmp/with-new-columns.parquet
-rw-r--r--  1 kszucs  wheel    67M Jul 22 14:47 /tmp/with-removed-columns.parquet

Bây giờ, hãy tải chúng lên Hugging Face để xem dữ liệu nào thực sự được truyền:

>>> pq.write_table(table_with_new_columns, "hf://datasets/kszucs/pq/orca-added-columns.parquet")
New Data Upload: 100%|███████████████████████████████████████████████|  575kB /  575kB,  288kB/s  
Total Bytes:  96.6M
Total Transfer:  575k

Chúng ta có thể thấy rằng chỉ các cột mới và siêu dữ liệu parquet mới được đặt trong chân trang của tệp được tải lên, trong khi dữ liệu gốc không được truyền lại. Đây là một lợi ích rất lớn của lớp lưu trữ Xet, vì nó cho phép chúng ta thêm các cột mới một cách hiệu quả mà không cần truyền lại toàn bộ tập dữ liệu.

Điều tương tự cũng áp dụng cho việc xóa các cột, như chúng ta có thể thấy bên dưới:

>>> pq.write_table(table_with_removed_columns, "hf://datasets/kszucs/pq/orca-removed-columns.parquet")
New Data Upload: 100%|███████████████████████████████████████████████| 37.7kB / 37.7kB, 27.0kB/s  
Total Bytes:  70.6M
Total Transfer:  37.7k

Để hiểu rõ hơn về những gì đã được tải lên, chúng ta có thể hình dung sự khác biệt giữa hai tệp parquet bằng công cụ ước tính loại bỏ trùng lặp tập dữ liệu:

from de import visualize

visualize(table, table_with_new_columns, title="With New Columns", prefix="orca")

Với các cột mới

Việc thêm hai cột mới có nghĩa là chúng ta có các trang dữ liệu chưa thấy phải được truyền (được đánh dấu màu đỏ), nhưng phần còn lại của dữ liệu vẫn không thay đổi (được đánh dấu màu xanh lá cây), do đó nó không được truyền lại. Lưu ý khu vực màu đỏ nhỏ trong siêu dữ liệu chân trang, khu vực này gần như luôn thay đổi khi chúng ta sửa đổi tệp parquet. Các số liệu thống kê dedup hiển thị <kích thước đã loại bỏ trùng lặp> / <tổng kích thước> = <tỷ lệ loại bỏ trùng lặp>, trong đó tỷ lệ nhỏ hơn có nghĩa là hiệu suất loại bỏ trùng lặp cao hơn.

Ngoài ra, hãy hình dung sự khác biệt sau khi xóa một cột:

visualize(table, table_with_removed_columns, title="With Removed Columns", prefix="orca")

Với các cột đã xóa

Vì chúng ta đang xóa toàn bộ các cột nên chúng ta chỉ có thể thấy những thay đổi trong siêu dữ liệu chân trang, tất cả các cột khác vẫn không thay đổi và đã tồn tại trong lớp lưu trữ, do đó chúng không được truyền lại.

3. Thay đổi các kiểu cột trong bảng

Một trường hợp sử dụng phổ biến khác là thay đổi các kiểu cột trong bảng, ví dụ: để giảm kích thước lưu trữ hoặc để tối ưu hóa dữ liệu cho các truy vấn cụ thể. Hãy thay đổi cột question_length từ kiểu dữ liệu int64 thành int32 và xem có bao nhiêu dữ liệu được truyền:

# first make the table much smaller by removing the large string columns
# to highlight the differences better
table_without_text = table_with_new_columns.drop(["question", "response"])

# cast the question_length column to int64
table_with_casted_column = table_without_text.set_column(
    table_without_text.schema.get_field_index("question_length"),
    "question_length",
    table_without_text["question_length"].cast("int32")
)
>>> pq.write_table(table_with_casted_column, "hf://datasets/kszucs/pq/orca-casted-column.parquet")
New Data Upload: 100%|███████████████████████████████████████████████|  181kB /  181kB,  113kB/s  
Total Bytes:  1.80M
Total Transfer:  181k

Một lần nữa, chúng ta có thể thấy rằng chỉ cột mới và siêu dữ liệu parquet được cập nhật đã được tải lên. Bây giờ, hãy hình dung bản đồ nhiệt loại bỏ trùng lặp:

visualize(table_without_text, table_with_casted_column, title="With Casted Column", prefix="orca")

Với cột đã truyền

Vùng màu đỏ đầu tiên cho biết cột mới đã được thêm vào, trong khi vùng màu đỏ thứ hai cho biết siêu dữ liệu đã được cập nhật trong chân trang. Phần còn lại của dữ liệu vẫn không thay đổi và không được truyền lại.

4. Nối thêm các hàng mới và nối các bảng

Chúng ta sẽ nối thêm các hàng mới bằng cách nối một lát cắt khác của tập dữ liệu gốc vào bảng.

table = orca[:100_000]
next_10k_rows = orca[100_000:110_000]
table_with_appended_rows = pa.concat_tables([table, next_10k_rows])

assert len(table_with_appended_rows) == 110_000

Bây giờ, hãy kiểm tra xem chỉ các hàng mới đang được tải lên vì dữ liệu gốc đã được biết đến lớp lưu trữ Xet:

>>> pq.write_table(table_with_appended_rows, "hf://datasets/kszucs/pq/orca-appended-rows.parquet")
New Data Upload: 100%|███████████████████████████████████████████████| 10.3MB / 10.3MB, 1.36MB/s  
Total Bytes:  106M
Total Transfer:  10.3M
visualize(table, table_with_appended_rows, title="With Appended Rows", prefix="orca")

Với các hàng đã nối

Vì mỗi cột nhận dữ liệu mới nên chúng ta có thể thấy nhiều vùng màu đỏ. Điều này là do đặc tả tệp parquet thực tế, nơi toàn bộ các cột được bố trí sau nhau (trong mỗi nhóm hàng).

5. Chèn / Xóa các hàng trong bảng

Đây là phần khó khăn vì các thao tác chèn và xóa đang chuyển các hàng hiện có, dẫn đến các khối cột hoặc trang dữ liệu khác nhau trong danh pháp parquet. Vì mỗi trang dữ liệu được nén riêng biệt, nên ngay cả một thao tác chèn hoặc xóa một hàng cũng có thể dẫn đến một biểu diễn cấp byte hoàn toàn khác nhau bắt đầu từ (các) hàng đã chỉnh sửa đến cuối tệp parquet.

Vấn đề cụ thể của parquet này không thể được giải quyết chỉ bằng lớp lưu trữ Xet, bản thân tệp parquet cần được viết theo cách giảm thiểu sự khác biệt giữa các trang dữ liệu ngay cả khi có các hàng được chèn hoặc xóa.

Hãy thử sử dụng cơ chế hiện có và xem nó hoạt động như thế nào.

table = orca[:100_000]

# remove 4k rows from two places 
table_with_deleted_rows = pa.concat_tables([
    orca[:15_000], 
    orca[18_000:60_000],
    orca[61_000:100_000]
])

# add 1k rows at the first third of the table
table_with_inserted_rows = pa.concat_tables([
    orca[:10_000],
    orca[100_000:101_000],
    orca[10_000:50_000],
    orca[101_000:103_000],
    orca[50_000:100_000],
])

assert len(table) == 100_000
assert len(table_with_deleted_rows) == 96_000
assert len(table_with_inserted_rows) == 103_000
>>> pq.write_table(table_with_inserted_rows, "hf://datasets/kszucs/pq/orca-inserted-rows.parquet")
New Data Upload: 100%|███████████████████████████████████████████████| 89.8MB / 89.8MB, 42.7kB/s  
Total Bytes:  99.1M
Total Transfer:  89.8M
>>> pq.write_table(table_with_deleted_rows, "hf://datasets/kszucs/pq/orca-deleted-rows.parquet")
New Data Upload: 100%|███████████████████████████████████████████████| 78.2MB / 78.2MB, 46.5kB/s  
Total Bytes:  92.2M
Total Transfer:  78.2M

Ngoài ra, hãy hình dung cả hai trường hợp để xem sự khác biệt:

visualize(table, table_with_deleted_rows, title="Deleted Rows", prefix="orca")
visualize(table, table_with_inserted_rows, title="Inserted Rows", prefix="orca")

Các hàng đã xóa

Các hàng đã chèn

Recommended for You

Chào `hf`- CLI Hugging Face nhanh hơn, thân thiện hơn ✨

Chào `hf`- CLI Hugging Face nhanh hơn, thân thiện hơn ✨

Giới thiệu một CLI mới cho Hugging Face nhanh hơn và thân thiện hơn.

TimeScope- Mô hình đa phương thức lớn cho video của bạn có thể đi được bao lâu?

TimeScope- Mô hình đa phương thức lớn cho video của bạn có thể đi được bao lâu?

Tìm hiểu về TimeScope, một chuẩn đánh giá mới để đánh giá khả năng của các mô hình đa phương thức lớn trong việc hiểu và lý luận về video dài.