프로젝트 공유 | MindNLP를 통해 HuggingFace 데이터 세트를 MindSpore에 접목하는 방법

저자: Lu Yufeng 출처: Zhihu

요약

MindNLP의 개발은 약 1년이 걸렸습니다. 전반적으로 많은 문제에 직면해 있으며 LLM이 가져온 일련의 영향과 과제도 동반됩니다. MindSpore를 기반으로 성장하는 후발 NLP 프레임워크로서 실제로 생태계를 확장하는 방법을 고려해야 합니다.

 

속담처럼, 이길 수 없다면 합류하세요. 하지만 오픈 소스 세계에서는 합류에 대해 이야기할 필요가 없습니다. 더구나 이틀 전 Pytorch2.1+Ascend가 공식적으로 발표된 순간, 생태 접목은 의심할 여지 없이 최고의 솔루션입니다. 잡담은 그만하고 본론으로 들어가겠습니다.

01

MindNLP 데이터 세트

MindNLP 설계 초기부터 기능적 융합 프로그래밍, 동적 그래프 기능, 데이터 처리 엔진 등을 포함하여 MindSpore의 모든 장점과 기능을 최대한 활용하기를 희망합니다. 여기서는 데이터 처리 엔진을 별도로 꺼내서 자세히 논의한다.

1.1MindSpore 데이터 처리 엔진

그림그림 1: MindSpore 데이터 엔진 파이프라인의 개략도

그림에서 볼 수 있듯이 데이터 엔진의 설계는 파이프라인[1]으로 Tensorflow의 Dataset 및 Pytorch의 Map-style Dataset과 매우 유사하며 주로 고성능 데이터 처리를 목표로 합니다.

여전히 모두가 순위를 새로 고치기 위해 작은 모델 수정과 작은 데이터 세트를 만들고 있는 시대에 데이터 전처리는 대개 오프라인으로 수행되므로 Python을 사용하여 최대한 유연하게 처리할 수 있으며 일반적으로 서버의 대용량 메모리는 모두가 한 번에 모든 데이터를 입력한 다음 이를 처리하기 위해 여러 프로세스를 열 것입니다. 그런 다음 Tensor에 로드하고 훈련을 위해 네트워크로 보냅니다. 그러나 그렇더라도 데이터 세트가 약간 더 크면 데이터 세트를 전처리하는 데 몇 시간 또는 며칠이 걸릴 수 있습니다.

파이프라인 방법은 다음과 같은 여러 기능에 중점을 둡니다.

1. 요청 시 로드

2. 비동기 처리

3. 병렬

그 중 1번과 2번에 대해 자세히 논의할 수 있다. 텍스트 데이터를 예로 들면, 가장 간단한 Python 로딩 전처리 로직(즉, Pytorch Dataloader)을 사용하면 전체 실행 흐름은 다음과 같습니다.

数据集全量加载至内存 -> 全量遍历并预处理 -> 单条数据打包Batch -> 循环返回每个Batch

Pipeline의 로딩 방법은 다음과 같습니다.

더 생생한 설명은 다음과 같습니다. 이제 데이터 세트 파일의 시작 부분을 가리키는 포인터가 있습니다. 매번 배치 크기의 데이터를 가져오고 포인터는 가져올 때까지 배치 크기만큼 전진합니다.

당연히 매번 적절한 양의 데이터만 가져오면 메모리 소모를 크게 줄일 수 있고, 전처리 과정에서 생성되는 중간 변수도 작은 크기로 압축할 수 있다. 또한 이 방법은 오프라인 데이터 전처리를 온라인으로 변환할 수 있습니다.

取Batch size条数据加载 -> Batch size条数据遍历并预处理 -> 返回一个Batch

그림그림 2: 데이터 처리 및 네트워크 컴퓨팅 파이프라인

데이터 처리 파이프라인은 지속적으로 데이터를 처리하고 처리된 데이터를 장치 측의 캐시로 보냅니다. 단계가 실행된 후 다음 단계의 데이터를 장치의 캐시에서 직접 읽습니다. 네트워크가 훈련되는 동안 데이터도 처리되어 각자의 임무를 수행합니다.

물론 이 방법은 양날의 검이기도 하지만 메모리 활용도와 성능을 향상시키는 동시에 사용 편의성 문제도 발생합니다. 그림 1의 맵은 각 데이터 전처리 작업을 구성한 후 직접 실행하고 결과를 반환하지 않습니다. 이는 세부적인 제어가 필요하고 특수한 조건이 많은 데이터에 적합하지 않으며 파이프라인 실행이 발생할 가능성이 매우 높습니다. .갑자기 이상이 발생했습니다.

그러나 LLM은 이러한 상황을 바꾸어 모든 작업이 Next Token Prediction으로 바뀌었고 모든 데이터 처리도 정리 + 토큰화로 바뀌었습니다. 데이터의 양이 많아 비즈니스 시나리오에서 데이터를 스트리밍하는 경우가 많습니다(이것이 바로 최적의 솔루션입니다.) 아마도 Pytorch가 파이프라인을 시작하고 HuggingFace 데이터세트도 파이프라인인 주된 이유일 것입니다.

1.2MindNLP 데이터 세트 지원 문제

앞서 언급했듯이 MindNLP의 데이터 처리는 MindSpore 데이터 처리 엔진을 완전히 사용하며 1년 이내에 20개 이상의 데이터 세트를 지원했습니다(torchtext 벤치마킹). 그러나 실제 사용에서는 다양한 NLP 작업에 이러한 데이터 세트 이상이 필요하고 오픈 도메인에 지속적으로 적응하기 어렵다는 것은 명백합니다.

또한 Shengsi MindSpore의 Dataset도 몇 가지 문제를 일으켰습니다. 주요 문제는 MindSpore Dataset이 다음과 같은 세 가지 유형의 로더를 설계했다는 것입니다.

1. 특정 데이터 세트 로더: IMDBDataset, EnWik9Dataset 등과 같은

2. 텍스트 추상 로더: TextFileDataset

3. 사용자 정의 로더: GeneratorDataset

1을 사용하면 지속적으로 적응을 추가해야 한다는 의미이고, 2를 사용하면 로드하기 전에 xml, json 등의 형식을 전처리해야 한다는 의미입니다. 이는 Pipeline의 고효율 설계 개념에 어긋납니다. 여전히 수동 조정이 필요합니다. 3을 사용하는 개발의 양은 그림 1의 첫 번째 단계가 우리가 원하는 것이 아니라는 것을 의미합니다. 그러나 데이터 세트를 신속하게 지원해야 하기 때문에 우리는 여전히 1+3 지원 방식을 선택했습니다.

이는 효율적이지 않으며 매번 별도의 조정이 필요합니다. 그렇다면 영구적인 해결책은 없을까요?

02

허깅페이스 생태 접목

MindNLP의 데이터 세트 로딩은 다음 두 가지 이상을 달성하려고 합니다.

1. 조정 없이 대규모 데이터 세트 지원

2. 효율적인 파이프라인 사용

스스로 할 수 없기 때문에 생태의 힘에 의지하자. HuggingFace는 Transformers 웨어하우스 외에도 AI 훈련의 다양한 프로세스를 위한 라이브러리를 개발했습니다. Datasets는 수년 동안 축적되었으며 수많은 데이터 세트를 지원합니다. 그리고 HuggingFace는 호스팅 서비스를 제공하기 때문에 많은 새로운 데이터 세트도 직접 제공됩니다. 데이터세트 허브에 직접 게시하세요. 데이터 세트를 사용하여 문제 1을 해결하고 두 번째 문제를 살펴보겠습니다.

실제로 MindSpore Dataset을 사용하는 대부분의 사람들은 기본적으로 두 가지 처리 방법을 선택합니다.

1. MindRecord에 대한 오프라인 전처리 후 MindDataset를 사용하여 로드

2. 데이터 세트를 메모리에 로드한 다음 특정 데이터 세트 로더/GeneratorDataset를 사용하여 로드합니다.

온라인 전처리를 할 수 있으려면 방법 1은 당연히 바람직하지 않기 때문에 HuggingFace Dataset을 접목하는 아이디어도 매우 간단합니다. 두 가지 아이디어를 고려하고 아래에서 논의하겠습니다.

2. 1 접목 데이터 세트 다운로드

그림그림 3: IMDB를 예로 들어 HuggingFace 데이터세트 예시

그림 3은 imdb 페이지의 스크린샷입니다. 그러면 HuggingFace Datasets를 사용하여 직접 다운로드한 다음 처리된 파일을 직접 읽어서 사용할 수 있습니다.

그림그림 4: TextFileDataset 인터페이스

TextFileDataset은 로드할 파일 경로나 경로 목록만 전달하면 된다는 것을 알 수 있습니다. 그런데 실제 작업 중에 문제가 발생했습니다. HuggingFace Datasets는 Apache Arrow 파일을 사용합니다.

그림그림 5: HuggingFace 데이터세트의 화살표 형식 소개

Apache Arrow [2]는 제로 복사가 가능한 언어 독립적인 다중 시스템 고성능 데이터 교환 형식 표준입니다. 이는 MindSpore의 Dataset을 직접적이고 간단하게 읽을 수 없다는 것을 의미합니다. pyarrow 라이브러리를 사용하여 작동할 수도 있지만 이로 인해 복잡성이 증가하고 로드하기 전에 전처리가 필요한 상태로 돌아갑니다. 하지만 Arrow 파일의 특성상 MindSpore의 Dataset이 더 적합한 것으로 나타났습니다.

2. 2 Arrow 형식의 장점

Multiwalker 환경에서는 이족 보행 로봇이 화물을 운반하고 오른쪽으로 걸어가려고 합니다. 여러 대의 로봇이 큰 화물을 운반하는데, 아래 그림과 같이 서로 협력해야 합니다.

HuggingFace는 Apache Arrow 형식을 사용하는데, 여기에는 몇 가지 확실한 장점이 있습니다.

1. Arrow의 표준 형식은 제로 복사 읽기를 허용하므로 모든 직렬화 오버헤드가 사실상 제거됩니다.

2. Arrow는 열 중심이므로 데이터 조각 또는 데이터 열을 쿼리하고 처리하는 것이 더 빠릅니다.

3. Arrow는 각 데이터 세트를 메모리 매핑된 파일로 처리합니다. 대용량 파일의 데이터 일부에 액세스할 때 전체 파일을 로드할 필요가 없으며 여러 프로세스가 메모리를 공유할 수 있습니다. 메모리 매핑을 사용하면 상대적으로 작은 장치 메모리를 가진 컴퓨터에서 대규모 데이터 세트를 사용할 수 있습니다. 전체 영어 Wikipedia 데이터 세트를 로드하는 데는 몇 MB의 RAM만 필요합니다.

4. 데이터를 로드할 때 스트리밍 로드에 대한 스트리밍 매개변수를 설정할 수 있습니다.

이번에는 MindSpore 데이터 엔진의 설계를 다시 살펴보겠습니다. 주문형 로딩, 온라인 처리 및 HuggingFace 데이터세트가 완벽하게 일치합니다.

2.3 MindNLP 적응

HuggingFace Datasets에서 로드한 화살표 파일 자체는 메모리 매핑된 파일이므로 메모리에 복사할 필요가 없으며, 인덱스 인덱스를 사용하면 완전히 로드되지 않으므로 데이터를 로드하는 소스로 직접 사용하여 전송할 수 있습니다. 사용하기 위해 GeneratorDataset에 직접 연결됩니다.

그림

그림 6: GeneratorDataset 인터페이스

GeneratorDataset을 구성하려면 주로 소스 데이터와 데이터의 각 열에 해당하는 열 이름이 필요합니다. 그림 3을 다시 보면 HuggingFace Datasets가 모든 열에 이름을 지정했음을 알 수 있습니다. 다음은 가로채는 핵심 코드입니다.

from mindspore.dataset import GeneratorDataset
from datasets import load_dataset as hf_load

......
def load_dataset(...):
    ds_ret = hf_load(path,
                     name=name,
                     data_dir=data_dir,
                     data_files=data_files,
                     split=split,
                     cache_dir=cache_dir,
                     features=features,
                     download_config=download_config,
                     download_mode=download_mode,
                     verification_mode=verification_mode,
                     keep_in_memory=keep_in_memory,
                     save_infos=save_infos,
                     revision=revision,
                     streaming=streaming,
                     num_proc=num_proc,
                     storage_options=storage_options,
                     )
    if isinstance(ds_ret, (list, tuple)):
        ds_dict = dict(zip(split, ds_ret))
    else:
        ds_dict = ds_ret

    datasets_dict = {}

    for key, raw_ds in ds_dict.items():
        column_names = list(raw_ds.features.keys())
        source = TransferDataset(raw_ds, column_names) if isinstance(raw_ds, Dataset) \
            else TransferIterableDataset(raw_ds, column_names)
        ms_ds = GeneratorDataset(
            source=source,
            column_names=column_names,
            shuffle=shuffle,
            num_parallel_workers=num_proc if num_proc else 1)
        datasets_dict[key] = ms_ds

    if len(datasets_dict) == 1:
        return datasets_dict.popitem()[1]
    return datasets_dict

처리 단계도 매우 간단합니다.

1. HuggingFace Dataset의 load_dataset를 사용하여 로드

2. 캡슐화를 위해 캡슐화된 전송 클래스를 사용하세요.

3. GeneratorDataset 전달

사용 편의성을 위해 load_dataset 인터페이스의 매개변수 설정을 HuggingFace Datasets와 정확히 동일하게 유지하지만 반환되는 것은 MindSpore 데이터 엔진에서 처리할 수 있는 클래스 또는 Dict입니다. Shengsi MindSpore의 데이터 처리 기능이 완료될 수 있습니다.

대중교통 클래스의 구조에 대해 간단히 이야기해 보겠습니다.

HuggingFace 데이터세트의 데이터 유형에는 Dataset 및 IterableDataset가 포함됩니다.

데이터 세트 객체에는 Dataset과 IterableDataset의 두 가지 유형이 있습니다. 사용하거나 생성하기 위해 선택하는 데이터 세트 유형은 데이터 세트의 크기에 따라 다릅니다. 일반적으로 IterableDataset는 게으른 동작과 속도 이점으로 인해 대규모 데이터 세트(수백 GB를 생각해보세요!)에 이상적인 반면 Dataset은 다른 모든 것에 적합합니다. 이 페이지에서는 Dataset과 IterableDataset의 차이점을 비교하여 적합한 데이터 세트 개체를 선택하는 데 도움을 줍니다.[3]

이 두 가지 유형의 데이터 세트를 순회하면 MindSpore의 데이터 처리 엔진에서 지원할 수 없는 dict가 반환되므로 다른 추가 작업을 추가하지 않고 dict의 데이터를 읽을 수 있도록 두 개의 전송 클래스가 만들어집니다. Dataset의 경우 TransferDataset 클래스를 생성하고 __getitem__ 메서드에서 읽습니다.

class TransferDataset():
    """TransferDataset for Huggingface Dataset."""
    def __init__(self, arrow_ds, column_names):
        self.ds = arrow_ds
        self.column_names = column_names

    def __getitem__(self, index):
        return tuple(self.ds[int(index)][name] for name in self.column_names)

    def __len__(self):
        return self.ds.dataset_size

스트리밍 데이터 IterableDataset의 경우 __iter__ 메서드에서 이를 읽고 TransferIterableDataset를 반복 가능한 객체로 구성해야 합니다.

class TransferIterableDataset():
    """TransferIterableDataset for Huggingface IterableDataset."""
    def __init__(self, arrow_ds, column_names):
        self.ds = arrow_ds
        self.column_names = column_names

    def __iter__(self):
        for data in self.ds:
            yield tuple(data[name] for name in self.column_names)

이제 약간의 노력으로도 HuggingFace Dataset을 완벽하게 접목할 수 있는 계획이 완성되었습니다. Paddle NLP 에 비해 접목 전략은 간단하고 우아합니다.

03

결론적으로

오픈소스 프레임워크로서 실제로 사용할 수 있는 오픈소스 리소스가 많이 있습니다. 소위 남북 생태계의 지속적인 확장이 반드시 내가 사용하고, 당신이 사용하고, 당신이 나를 사용한다는 의미는 아닙니다. , 당신은 행복하고 걱정이 없습니다. 이번에 HuggingFace 데이터 세트는 Shengsi MindSpore 실용적인 공유에 접목되어 Shengsi MindNLP에 대한 더 깊은 이해를 제공하고 Shengsi MindSpore 생태계를 확장하는 데도 도움이 될 것입니다.

참고자료

[1] https://www.mindspore.cn/docs/zh-CN/r2.1/design/data_engine.htm

[2] https://arrow.apache.org/

[3] https://huggingface.co/docs/datasets/about_mapstyle_vs_iterable

 

1990년대에 태어난 프로그래머가 비디오 포팅 소프트웨어를 개발하여 1년도 안 되어 700만 개 이상의 수익을 올렸습니다. 결말은 매우 처참했습니다! Google은 Flutter, Dart 및 Python 팀의 중국 코더의 "35세 저주"와 관련된 정리해고를 확인했습니다 . | Daily Windows 1.0용 Arc Browser가 3개월 만에 공식적으로 GA Windows 10 시장 점유율이 70%에 도달했으며 Windows 11 GitHub는 AI 기본 개발 도구 GitHub Copilot Workspace JAVA를 계속해서 출시했습니다 . OLTP+OLAP을 처리할 수 있는 유일한 강력한 유형의 쿼리입니다. 우리는 너무 늦게 만났습니다 .
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4736317/blog/11072529