태그 : Backend.AI

  • 래블업-인텔, Backend.AI에서 인텔® Gaudi® 2 및 인텔® Gaudi® 3 AI 가속기에 대한 지원 발표

    By 래블업 주식회사

    서울, 대한민국 — 래블업은 SuperComputing 2024에서 Backend.AI가 인텔® Gaudi® 2 및 인텔® Gaudi® 3 AI 가속기를 지원한다고 발표했습니다. Backend.AI가 지원하고 있는 NVIDIA, Rebellions, FuriosaAI, AMD 등의 AI 가속기 공급업체 라인업에 인텔을 추가함으로써, 래블업은 시장에서 가장 다양한 AI 가속기와 GPU를 고객에게 제공하게 되었습니다. 래블업은 이를 통해 Backend.AI 플랫폼의 경쟁력을 강화하고 고객들에게 더욱 폭넓은 선택권을 부여하게 되었습니다.

    *2024년 11월 현재, Backend.AI는 인텔® Gaudi® 2 AI 가속기를 지원하고 있습니다.

    *인텔® Gaudi® 3 AI 가속기에 대한 지원은 2025년 상반기에 예정되어 있습니다.

    래블업과 인텔은 인텔® Gaudi® 2 및 인텔® Gaudi® 3 AI 가속기가 보여줄 수 있는 최상의 성능을 Backend.AI에서 제공하기 위해 긴밀히 협력해 왔으며, 이러한 협력의 결과로 Backend.AI의 인텔® Gaudi® 2 및 인텔® Gaudi® 3 AI 가속기 지원을 발표합니다.

    Backend.AI의 Sokovan™을 통한 강력한 컨테이너 오케스트레이션

    Sokovan™은 멀티테넌트 및 멀티노드 확장 시나리오에 적합한 독립 실행형 오픈소스 컨테이너 오케스트레이터입니다. Sokovan™은 최신 하드웨어 가속 기술에 최적화되어 있으며, 사용자가 정의할 수 있는 작업 스케줄링 및 노드 할당 정책을 통해, AI 성능 저하 없이 단일 클러스터에서 인터랙티브, 배치, 그리고 서비스 워크로드가 혼합된 형태의 하이브리드 환경까지도 지원합니다.

    AI 가속기를 최대치로 활용하고, 여러분의 잠재력을 실현하세요.

    복잡한 비즈니스에서는 탁월한 AI 성능과 우수한 관리 용이성이 성공의 열쇠입니다. 인텔의 최신 제품인 인텔® Gaudi® 3 AI 가속기는 강력한 AI 성능과 기능을 제공합니다. 서비스형 플랫폼인 Lablup Backend.AI는 엔터프라이즈급 AI 환경에 최적화된 다양한 기능을 제공합니다.

    인텔® Gaudi® 2 및 3 플랫폼과 깊숙이 결합된 우리의 기술 혁신

    이미 비즈니스 환경에 인텔® Gaudi® 2 AI 가속기 또는 인텔® Gaudi® 3 AI 가속기를 도입한 고객과 향후 인텔® Gaudi® 2 & 3 플랫폼을 도입할 고객 모두 래블업 Backend.AI가 인텔® Gaudi®에 대해 지원하는 다양한 혜택을 누릴 수 있습니다. 인텔® Gaudi® 2 & 3 플랫폼과 함께 동작하는 Backend.AI 기능의 일부를 확인해 보세요.

    카드 레벨 가속기 할당 (Card-level accelerator allocation)

    사용자가 의도한 만큼의 실제 가속기를 제공하여 인텔® Gaudi® 2 & 3 AI 가속기 클러스터 워크로드를 극대화합니다. 예를 들어, 고객은 기존에 선호하는 플랫폼에서 모델을 실행 및 훈련한 다음 인텔® Gaudi® 2 및 3 플랫폼에서 서비스를 제공하거나 그 반대의 경우도 가능합니다.

    외부 스토리지 할당 (External storage allocation)

    통합 스토리지 솔루션을 최대한의 성능으로 활용하세요. 사용자의 개입 없이 공급업체별 파일 시스템 가속 기능을 활용하세요. Backend.AI는 Dell PowerScale, VAST Data, WEKA, NetApp 등과 같이 널리 사용되는 주요 플랫폼을 지원합니다.

    멀티스케일 워크로드 (Multi-scale workloads)

    소규모 모델을 실행할 수 있는 단일 카드 AI 워크로드부터 대규모 모델을 실행할 수 있는 멀티 노드 멀티 카드 AI 워크로드까지, 어떤 환경에서도 Backend.AI는 최고의 성능을 보장합니다. 11월 1일 현재, Backend.AI는 단일 카드 AI 워크로드와 단일 노드, 다중 카드 AI 워크로드를 실행할 준비가 되었습니다. 다중 노드, 다중 카드 AI 워크로드 지원은 올해 마무리될 예정입니다.

    추론 통계 데이터 관리 (Inference statistics management)

    AI 프레임워크에서 제공하는 성능에 대한 최신의 상세한 지표를 모니터링하세요. Backend.AI는 하드웨어의 정보뿐만 아니라 소프트웨어의 통계 데이터를 가져올 수 있기 때문에, 관리자가 메트릭을 심층적으로, 쉽게 분석할 수 있도록 만들어줍니다.

    규칙 기반 추론 레플리카 자동 스케일링 (Rule-based inference replica auto scaling)

    시스템이 리소스 사용량을 스스로 최적화할 수 있습니다. 다양한 하드웨어 및 소프트웨어 수치 모니터링을 통해, 관리자의 수동 개입 없이도 다양한 사용자의 사용 패턴에 대응합니다.

    *현재 개발 중(2024년 12월 개발 완료 목표)

    NUMA 인식(NUMA-aware) 리소스 할당

    CPU 소켓이 여러 개 있고 각 소켓마다 여러 개의 가속기가 있는 경우 단일 노드 내에서 CPU 간 및 PCIe 버스 오버헤드를 제거하여 베어메탈에 가까운 성능을 달성할 수 있습니다.

    유저 및 프로젝트 기반의 스토리지 할당량 관리

    사용자 또는 단일 프로젝트당 데이터 스토리지 할당량을 제한하여 예산 효율적이고 간편하게 데이터 공간을 관리할 수 있습니다.

    Hugepage 메모리 할당 지원

    주소 변환 (Address Translation)의 오버헤드를 줄이기 위해 더욱 큰 메모리 페이지 (Hugepage)를 사용하되, 그 수를 줄여 AI 가속기를 사용할 때의 CPU 오버헤드를 최소화합니다. 해당 기능에 대한 Backend.AI의 지원은 올해 안에 마무리 될 예정입니다.

    이외에도 더 많은 기능들이 기다리고 있습니다

    래블업은 인텔과 지속적으로 소통하며 Backend.AI의 가능성을 넓혀가고 있습니다. 더 많은 기능이 아직 개발 중이며, 곧 선보여질 예정입니다. Backand.AI와 인텔® Gaudi® 3 AI 가속기를 활용하여 여러분의 클러스터 활용도를 높여보세요.

    인텔® Gaudi® 3 AI 가속기의 성능을 Backend.AI에서 누리세요

    Backend.AI는 인텔® Gaudi® 3 AI 가속기의 성능을 최대로 끌어낼 수 있도록 설계되었습니다. 인텔® Gaudi® 3 AI 가속기는 기본적인 컴퓨터 비전 및 NLP 모델, 심지어는 거대언어모델 및 멀티모달 모델까지 최첨단의 (state-of-the-art) 모델을 학습하고, 실행할 수 있도록 합니다. 인텔® Gaudi® 3 AI 가속기는 가격 대비 훌륭한 성능과 운영 효율성을 제공합니다. 클라우드, 혹은 데이터 센터 그 어디에서도 효율적인 확장성을 제공하도록 설계된 인텔® Gaudi® 3 AI 가속기는 AI 업계에 그 어느 때보다 필요한 선택지를 제공합니다.

    여러분의 작업을 최고 수준의 사용자 인터페이스에서

    다른 시스템과 달리, Backend.AI는 시스템 관리자가 최대한 쉽게 시스템을 제어할 수 있도록 설계되었습니다. 사용자 친화적인 인터페이스 덕분에 관리자는 몇 번의 클릭과 입력만으로 시스템을 관리할 수 있습니다. Backend.AI WebUI는 다양한 산업 환경에서 검증된 고객들이 널리 사용하고 있으며, 고객들은 CLI(명령줄 인터페이스)를 사용하지 않고도 GUI를 통해 수행할 수 있는 다양한 기능에 만족하고 있습니다.

    Backend.AI를 통해 인텔® Gaudi® 2 및 인텔® Gaudi® 3 AI 가속기를 '관리하기 쉬운' 플랫폼으로 만들어보세요.

    래블업은 과학자, 연구원, DevOps, 기업 및 AI 애호가들이 AI 서비스를 효율적이고, 확장 가능한 형태로 사용할 수 있도록 하기 위해 최선을 다하고 있습니다. 우리는 인텔과 함께 오늘날 널리 사용되고 있는 생성 AI 및 딥 러닝 기반 서비스의 성공을 위해 긴밀히 협력하고 있습니다. 다양한 고객들로부터 검증된 래블업의 기술을 통해, 인텔® Gaudi® 2 및 인텔® Gaudi® 3 플랫폼과 Backend.AI는 하드웨어 수준 통합을 제공하기 위해 노력하고 있습니다.

    인텔® Gaudi® 3 AI 가속기 소개

    검증된 MLPerf 벤치마크 성능을 갖춘 고효율 인텔® Gaudi® 플랫폼을 기반으로 하는 인텔® Gaudi® 3 AI 가속기는 까다로운 훈련 및 추론을 처리하도록 설계되었습니다.

    데이터 센터 또는 노드부터 메가 클러스터에 이르는 클라우드에서 대규모 언어모델, 멀티 모달 모델, 엔터프라이즈 RAG와 같은 AI 응용 프로그램을 지원하며, 이미 보유하고 있을 가능성이 있는 이더넷 인프라에서 모두 실행됩니다. 단일 가속기가 필요하든 수천 개의 가속기가 필요하든, 인텔® Gaudi® 3는 AI 성공에 중요한 역할을 할 준비가 되어 있습니다.

    인텔® Gaudi® 3에 대해 자세히 알아보려면, intel.com을 방문하세요.

    래블업 Backend.AI 소개

    Backend.AI는 여러분의 AI 비즈니스를 극대화시키기 위해 시중의 다양한 GPU와 AI 가속기를 지원하고 있으며, 사용자들이 편리하게 사용할 수 있도록 직관적인 사용자 인터페이스를 제공하고 있습니다. 고객은 가장 작은 수준의 언어 모델부터 거대 언어모델까지 AI 모델의 크기에 구애받지 않고 AI 모델을 효과적으로 구축, 학습, 제공할 수 있어 서비스 개발과 운영의 비용, 복잡성을 크게 줄일 수 있습니다. Backend.AI는 생성형 AI와 가속 컴퓨팅의 잠재력을 최대한 이끌어내 최첨단 기술을 통해 여러분의 비즈니스를 혁신할 수 있는 열쇠가 되어가고 있습니다.

    Backend.AI®에 대해 자세히 알아보려면, backend.ai를 방문하세요.

    31 October 2024

  • Uncharted AI: 대 AI 시대

    By 래블업 주식회사

    해당 글은 2024년 9월 24일, lab | up > /conf/4에서의 신정규 대표 기조 연설을 요약한 글입니다.

    2024년 9월 24일, 래블업의 4번째 컨퍼런스, lab | up > /conf/4가 열렸습니다. 래블업의 직원들 뿐만 아니라 다양한 외부 연사 분들이 참여하여 자리를 빛내 주셨는데요, 해당 행사의 기조연설은 래블업의 CEO, 신정규 대표가 맡아 주었습니다.

    Photo by 'iT dongA'

    이번 글을 통해 신정규 대표가 기조 연설을 통해 소개한 AI 시대의 발전, 앞으로의 래블업의 방향성, 그리고 래블업이 개발하고 있는 제품들에 대한 업데이트와 몇가지 새로운 제품들에 대해 소개하고자 합니다.

    Uncharted Waters

    이번 기조연설의 제목인 <Uncharted AI - 대 AI시대'는 많은 분들이 추억하는 명작 게임, '대항해시대 - Uncharted Waters'에서 가져왔습니다. 대항해시대는 단순한 게임이 아닙니다. 이 시대는 실제 존재했던 우리 지구촌의 역사이기도 하며, 중요한 의미를 가지는 시대입니다.

    대략 15세기 이후, 대항해시대에는 수많은 사람들이 향신료를 찾아 먼 바다 너머로 여행을 떠났습니다. 지금은 어디서나 구할 수 있는 '후추'를 찾기 위해서였죠. 우리는 그 시대에 태어나진 않았으니, 저는 후추를 찾기 위해 열심히 게임을 했죠. 지금 생각하면 그깟 향신료가 뭐라고 싶지만, 당시 수많은 모험가들은 자신들의 목숨을 걸어가며 향신료를 찾아 떠났습니다.

    Uncharted AI

    그 시절 향신료를 찾아 바다 건너 목숨을 걸고 모험을 떠났던 수많은 사람들처럼, 지금 우리는 인공지능(AI)이라는 새로운 시대를 맞이하여, 인공지능을 발전시키기 위해 목숨을 걸고, 다양한 파트너들과 함께 노력해 나가고 있습니다. 왜 이런 노력이 필요하느냐, 그것은 바로 '접근성' 때문입니다. 후추를 내 집 앞마당에서 수확할 수 있다면, 굳이 바다를 건너야 할 필요가 없겠죠. 새로운 시대가 시작되었을 때, 이런 접근성의 차이가 누군가에게는 기술격차를 만들어내기도 하고, 누군가에게는 도전의 기회를 만들어내기도 하는 것입니다. 새로운 기술로 인해 생겨난 기술 격차는 래블업이 새로운 시대를 열어젖히는 원동력이 되었습니다.

    래블업의 모토는 2015년 창립 초기부터 명확했습니다. 우리는 Make AI Accessible을 목표로 기술의 접근성을 높이고, 장벽은 낮추는 것을 핵심 미션으로 삼았습니다. AI 기술을 단순히 API 형태로만 사용하는 것이 아니라, 사용하는 사람이 인프라스트럭처에 대한 기술 자체를 이해하고 사용할 수 있도록 하자. 이를 통해 수많은 사람들이 AI에 접근하는 장벽을 낮추자는 것을 목표로 삼았습니다.

    AI 분야가 발전하며 새로운 도전이 나타났습니다. 바로 스케일의 문제입니다. AI 기술이 다루는 데이터의 규모가 커지고, 연산의 규모가 커지며 과거에는 싱글 노드로 감당했던 수준을 넘어 멀티 노드가 되고, 수십 수천대, 몇 만대의 GPU를 다루는 시대가 되었죠. 동시에, AI는 작아지고 있기도 합니다. 올해 초 삼성이 공개한 Galaxy AI, 애플이 얼마 전 공개한 Apple Intelligence처럼 여러분의 손 안에서 온디바이스로 동작하는 AI도 있고, IoT 센서와 같은 온도계에도 AI가 돌아가고 있습니다.

    그러니까, 더 높은 전력과 많은 리소스를 사용하여 AI를 돌리려는 시도가 이어짐과 동시에, 더 낮은 전력과 적은 리소스를 사용해서 AI를 돌리려는 시도들도 꽃을 피우고 있는 것입니다. 기존에 우리가 AI라고 생각했던 어떠한 범위가 있다면, 그 범위가 위로 (크게) 확장되는 동시에 아래로 (작게) 축소되고 있기도 한 것입니다. 그리고 양 쪽 방향으로 스케일 밴드를 이동시키는데 필요한 기술은 완전히 다르죠.

    래블업이 사업을 시작했던 2015년만 해도 지포스 GTX970만으로도 모델을 만들 수 있었지만 지금은 워크로드의 규모가 너무나도 빨리 커지고 있기 때문에, 이 워크로드의 증가가 무어의법칙이라 부르는 반도체의 성능발전을 넘어서게 된 지가 벌써 4~5년이 지났습니다. 그렇기에 이젠 수직으로 칩의 성능이 증가하는 것이 아니라, 여러개의 칩을 묶어 수평으로 활용하는 시대가 된 것이죠.

    Make AI "Scalable"

    최근 4년 사이 AI 분야에서의 분산 컴퓨팅 패러다임이 급격히 진화하고 있습니다. 패러렐 프로세싱을 넘어 이제는 다양한 종류의 연산이 동시다발적으로 일어나고 있습니다. 데이터를 처리하고, 모델을 학습시키고, 서비스를 제공하는 등 이질적인 작업들이 한데 어우러집니다. Heterogeneous computation resource가 동시에 요구되고 일부는 데이터베이스, 일부는 트레이닝, 일부는 데이터 가공, 일부는 플릿 관리 및 RAS… 더 많은 부분이 서비스 스택에 가까워졌습니다.

    게다가 이 모든 작업에 GPU와 같은 가속기가 필수불가결해졌습니다. 이제 우리는 단순히 CPU와 GPU를 분리해서 사용하는 게 아니라, 두 자원을 더욱 긴밀하게 연동해야 합니다. 기존의 GPU 기반 서비스 + CPU 서비스로 나누는 대신, 두 종류의 리소스가 더 강하게 결합되기 시작한 것이죠. 이같은 변화의 근본 원인은 모든 서비스에 GPU가 필요해졌기 때문입니다. 이런 상황에서는 전력, 네트워크, 데이터 등 물리적인 제약 뿐 아니라, 하드웨어 불안정, 플랫폼 관리, 소프트웨어 결함 등 비물리적인 제약들까지도 모두 병목으로 작용하게 됩니다. 래블업은 이런 스케일링의 장벽을 걷어내는 데 주력하고 있습니다.

    이것이 바로 래블업이 올 해 새로 내건 목표, Make AI Scalable입니다. 우리는 가속장치부터 싱글 노드를 거쳐 하이퍼스케일에 이르는 모든 범위에서 AI 워크로드의 확장성을 추구합니다. 스케일링에 방해되는 모든 요소를 없애고, 스케일링에 필요한 모든 요소를 더합니다. 그리고 그 과정에서 AI 기술에 대한 접근 장벽을 계속 허물고 있기 때문에, 우리의 새로운 목표는 "Make AI Accessible"이라는 기존 목표에서 이어지죠.

    그동안 래블업은 AI를 Accessible하고, Scalable하게 만들기 위한 다양한 노력을 이어왔고, 이는 다양한 혁신으로 이어졌습니다. 이러한 결과로, Backend.AI로 운영되는 엔터프라이즈 GPU 유닛의 숫자는 13,000개에 육박하며, 단일 사이트 기준 1,500개 이상의 GPU를 관리하고 있는 경우도 있습니다. 래블업의 제품을 사용하는 팀(고객사)도 100여개 이상으로 확장되었죠. 클라우드 서비스, AI 가속기 테스트베드, 자율주행 등 다양한 분야에서 Backend.AI는 AI의 핵심 인프라로 자리잡았습니다.

    이러한 대규모 확장은 기술적 난이도를 크게 높였습니다. 우리는 한 대의 서버부터 수천 대의 클러스터까지, 모든 스펙트럼을 아우르는 기술을 개발해야만 했습니다. 그 기술은 앞에서 말씀드렸듯, "스케일링에 방해되는 모든 요소를 없애고, 스케일링에 필요한 모든 요소를 더하는" 것입니다. 이번 기회를 통해 래블업이 지금까지 이뤄낸 혁신과 앞으로 이뤄낼 혁신, 우리가 그리고 있는 미래에 대해 공유드리겠습니다.

    Open Source

    먼저, 래블업은 오픈소스 생태계에 깊이 관여하고 있는 오픈소스 기업입니다. Backend.AI, Callosum, aiodocker, aiomonitor(, aiotools), Raftify등 다양한 프로젝트를 개발하고 공개하고 있습니다. 오픈소스는 우리의 DNA에 새겨져 있습니다. 우리가 만들어 공개하거나 기여하는 오픈소스들의 배포 이식성은 적은 노력으로도 다양한 온프레미스 환경을 커버하는 래블업의 핵심 경쟁력이기도 합니다. Backend.AI의 온프레미스 환경에 대한 지원, 클라우드 환경과의 호환성 등은 모두 오픈소스 경험으로부터 래블업이 확보한 역량이라 할 수 있습니다.

    Backend.AI CLI Installer: TUI로 경험하는 쉬운 설치 경험

    Backend.AI CLI Installer는 Backend.AI에 대한 접근성을 높이기 위한 새로운 오픈소스입니다. TUI (Text-based User Interface)를 도입하여 쉽게 Backend.AI를 설치할 수 있고, 패키지 기반 설치를 자동화하며, 자동 설치용 메타 설정을 지원합니다.

    bndev: 나만의 AI 인프라를 손쉽게

    단순 패키지 기반 설치가 아니라, 내가 직접 뜯어 고치며 해킹하고 싶은 분들을 위해 bndev라는 개발도구를 공개했습니다. bndev를 통하면 복잡한 Backend.AI 개발 환경을 손쉽게 구축하고 관리할 수 있습니다. 모두가 자신만의 AI 인프라를 가질 수 있고 고칠 수 있게 되는 것이죠.

    Backend.AI Core

    Backend.AI는 매 년 3월과 9월에 메이저 버전 릴리즈를 진행합니다. 2024년 3월에는 24.03 버전을 릴리즈했고, 곧 24.09 버전의 릴리즈를 눈앞에 두고 있습니다. 24.09 버전과 그 이후의 버전에도 영향을 미칠 요소로, Backend.AI Core에 큰 변화가 있습니다. 해당 내용을 소개드리겠습니다.

    Key Updates

    • NVIDIA NGC(NVIDIA GPU Cloud) NIM(Nemo Infrerence Microservice) 실행 지원: 라이선스 기반 컨테이너 이미지 적재 등 NGC의 주요 기능을 Backend.AI에서 활용할 수 있습니다.
    • Intel Gaudi2, Rebellions ATOM+, Furiosa RNGD 등 신규 가속기 지원 대폭 확대: Backend.AI 내에서 고객의 워크로드 특성에 맞는 최적의 AI 가속기를 유연하게 선택할 수 있습니다.
    • Backend.AI 모델 스토어, 브라우저, 서빙 기능 정식 출시(GA): MLOps의 핵심 기능을 아우르는 원스톱 솔루션으로, 고객이 손쉽게 AI 모델을 검색하고, 자신의 워크로드에 바로 배포할 수 있습니다.
    • 작업 스케줄링 체계 강화: 새로운 Priority Scheduler를 통해 작업의 우선순위를 스케줄러와 독립적으로 지정할 수 있으며, 중요도가 높은 작업을 빠르고 안정적으로 처리할 수 있습니다.
    • Agent Selector 개념 도입: Agent Selector는 스케줄러가 선택한 작업을 실제로 어떤 노드에서 실행할지를 결정하는 역할을 합니다. 이제 이 부분도 독립된 플러그인 형태로 쉽게 사용자 정의할 수 있습니다. 이를 활용하면 작업을 각 노드의 전력 사용량이나 온도 등 다양한 기준에 따라 분산 배치할 수 있습니다. 노드 간 부하를 균등하게 맞추고, 전력 효율을 높이는 등 인프라 운영을 최적화하는 데 큰 도움이 될 것으로 기대합니다.
    • 자체 Docker 네트워크 플러그인 개발: 대규모 데이터 처리를 위한 GPUDirect Storage 지원 범위를 넓혀, 단일 노드 안에서의 데이터 이동 병목을 최소화하였습니다.
    • 컨테이너 간 통신을 위한 Cilium 기반의 네트워킹 스택 도입: 대규모 분산 학습에 도움이 되는 기능으로, 해당 스택을 통해 기존 대비 30%의 네트워크 성능 향상을 확인했습니다.
    • OIDC(OpenID Connect) 기반의 통합 인증 체계 확보: 단일 계정으로 Backend.AI를 포함한 다양한 인프라 서비스에 접근, 계정 관리가 대폭 간소화됩니다.
    • 엔터프라이즈 환경 지원 대폭 확대: GitLab, GitHub Enterprise, AWS ECR 등 다양한 PrivateContainer Registry와의 연동이 가능하며, 기업 내부 레거시 자원과 클라우드를 모두 아우르는 하이브리드 구성도 쉽게 구성할 수 있습니다.

    위와 같은 업데이트를 기반으로, Backend.AI는 고성능 컴퓨팅(HPC)을 위한 기능과 엔터프라이즈를 위한 기능을 모두 갖춘 차세대 AI 인프라스트럭처로서의 면모를 확장해 나가고 있습니다. 향후 자세한 업데이트 내용은 Backend.AI 24.09 출시와 함께 확인할 수 있습니다.

    Next-gen Sokovan

    래블업은 내년 초 출시를 목표로 차세대 Sokovan에 대한 작업도 이어나가고 있습니다. 차세대 Sokovan에 대한 내용을 간략하게 공개합니다.

    • 쿠버네티스를 지원하는 듀얼 엔진 아키텍처: 기존의 독자적인 클러스터 관리 체계와 함께, 쿠버네티스 네이티브 서비스로도 동작하게 됩니다. 이를 위해 쿠버네티스 오퍼레이터 프록시를 통한 가속기 관리 기능을 탑재할 예정입니다. NVIDIA와 AMD의 각종 디바이스 플러그인, 인텔 GPU 플러그인 등을 매끄럽게 연동, 업계 표준을 지원하게 되는 것이죠.
    • HA 구성 시 Raftify를 활용한 DB 부하 분산 기술 적용: 메타데이터 서비스에 대한 병목 현상을 최소화하고, 수만 대 규모 클러스터에서도 안정적인 운영이 가능해집니다.
    • 거대언어모델 서빙을 위한 자동 스케일링 강화: 단순 리소스 사용량뿐 아니라, 요청 패턴과 레이턴시 등 API 메트릭을 종합적으로 분석해 최적의 스케일링을 수행합니다.
    • 프로젝트 단위 강화: 데이터 세트와 모델, 파이프라인 등을 하나의 단위로 묶어 관리할 수 있게 됩니다. 이를 통해 세밀한 **RBAC(Role-Based Access Control)**를 적용해 다양한 협업 시나리오를 지원하는 것이 목표입니다.
    • 엔터프라이즈 고객을 위한 관리 기능 강화: 통합 로깅과 모니터링은 물론, 규제 준수를 위한 감사 로그 추적 기능을 갖추게 됩니다.

    이 모든 변화는 한 가지 목표를 염두하고 만들어지고 있습니다. 바로 고객의 AI 프로젝트를 가속화하는 것이죠. 래블업의 개발팀은 신규 AI 가속기를 비롯해 각종 쿠버네티스 기반 솔루션과의 연계를 통해 Backend.AI Core 및 MLOps 기능의 성숙도를 한 층 더 높여갈 수 있을 것으로 기대하고 있습니다. 더욱 폭넓은 역할을 수행하게 될 차세대 Sokovan의 여정을 기대해 주시기 바랍니다.

    Backend.AI WebUI

    빠른 시일 안에, Backend.AI WebUI는 새 옷을 입습니다. 사용자 관점에서 Backend.AI의 첫인상을 결정짓는 가장 중요한 요소는 바로 사용자 인터페이스일 것입니다. 우리는 그동안 WebUI의 중요성을 깊이 인식하고, 꾸준하게 혁신을 거듭해 왔습니다. 다양한 사용자 경험 테스트를 위해 작년에는 ML Desktop을, 올해 초에는 GenAI Desktop을 출시하기도 했죠. 최근에는 Neo Session Launcher를 통해 사용자 친화적인 UI를 제품에 적용하기도 했습니다.

    WebUI의 세 번째 새로운 변화, WebUI Neo를 소개합니다. Vice Versa Design Studio와의 긴밀한 협업을 통해 풍부한 사용자 경험을 제공하는 것을 목표로 설계되었죠. 이 새로운 디자인 언어는 처음부터 끝까지 사용자를 고려하고 설계되었습니다. Backend.AI 리뉴얼에 맞춰 UI/UX 전반을 재설계하여 한 층 세련되고, 미래지향적인 분위기를 느낄 수 있을 것입니다.

    WebUI Neo는 '인지 부하의 감소'와 '시각적 은유의 일관성 유지'를 콘셉트로 설계되었습니다. '인지 부하의 감소' 측면에서, 우리는 사용자가 복잡한 정보를 입력하거나, 탐색해야 하는 상황을 최소화하고자 하였습니다. 예를 들어, 대규모의 실험을 설정하는 경우 수십가지의 옵션을 한 번에 늘어놓는 것 보다는 순차적으로 정보를 노출하여 단계별로 확인 가능한 정보의 양을 제한했습니다.

    '시각적 은유의 일관성 유지' 측면에서는, 실험과 모델, 데이터 세트 등 유사한 개념에 대해 화면 구성, 아이콘, 색상에 이르기까지 UI/UX 요소를 유사하거나 동일한 디자인 패턴으로 구성하여 사용자가 한 번 익힌 사용법을 유사 기능을 사용하며 재학습할 필요 없이 재사용할 수 있도록 하였습니다. WebUI Neo는 Core 및 엔터프라이즈에 걸쳐 모두 적용됩니다.

    이러한 혁신성을 인정받아, WebUI Neo는 이번 달 서울특별시와 서울디자인재단에서 주관한 중소기업 산업디자인개발 지원사업에서 총 4개의 컨소시엄에게만 주어지는 우수상을 수상했습니다.

    WebUI Neo는 Backend.AI 24.09 업데이트에 바로 포함되지는 않으며, 올해 연말 정식 출시를 목표로 개발과 테스트가 진행되고 있습니다. WebUI의 첫 버전부터 사용되었던 코드베이스인 웹 컴포넌트 기반에서 리액트 기반으로 이전하는 작업도 한창 마무리 중에 있습니다. WebUI Neo는 단순히 과거의 기능을 재포장하는 선에서 그치지 않습니다. 머신러닝 워크플로와 긴밀하게 연계된 새로운 기능들이 지속적으로 추가될 예정이고, Backend.AI가 추구하는 고도의 자동화와 사용 편의성을 구현하기 위한 기반이 될 것입니다. AI 인프라스트럭처의 복잡성을 넘어 모두가 AI 인프라스트럭처를 쉽게 이해하고 그 혜택을 누리는 세상, 래블업이 WebUI Neo를 통해 그려가는 미래입니다.

    Lablup Enterprise

    Backend.AI Enterprise 를 중심으로 하는 Lablup Enterprise 의 핵심은 ___ made easy 로 표현할 수 있습니다. Lablup Enterprise는 디바이스 드라이버 레벨부터 AIOps까지 엔드 투 엔드 기술로 깊은 단계에서의 AI 기술 혁신을 쉽게 제공하는 것을 목표로 합니다. 저희는 총 3개의 ___ made easy 콘셉트를 잡고 있는데요, 첫번째는 "Scaling made easy", 두번째는 "Acceleration made easy", 마지막은 "Inference made easy" 입니다.

    Scaling made easy: FastTrack 2, Finetun.ing, Cluster Designer

    FastTrack 2

    래블업이 24.09와 함께 릴리즈하는 FastTrack 2는 대규모의 AI 프로젝트를 위한 자동화 솔루션입니다. 프로젝트 그룹 기반의 파이프라인 관리 기능을 제공해, 복잡한 워크플로를 손쉽게 정의하고 실행할 수 있습니다. 다양하고, 재사용 가능한 템플릿을 제공하여 반복적인 작업을 최소화시키는 것을 큰 특징으로 가지고 있습니다. 또한, FastTrack 2에서는 외부 파트너와의 연계를 통해 자원을 더욱 유용하게 활용할 수 있게 됩니다. 파트너사의 모델 압축 노드와 모델 서빙 서비스를 파이프라인에 추가할 수 있습니다.

    Finetun.ing

    Finetun.ing은 FastTrack으로 개발된 클라우드 파인튜닝 서비스이며, 일반적으로 제공되는 파인튜닝 서비스와 다르게 데이터를 직접 준비할 필요가 없다는 큰 특징이 있습니다. 보통은 기반이 되는 데이터를 업로드해서 모델을 파인튜닝하는 시나리오를 많이 생각하는데, Finetun.ing은 사용자가 인터랙티브 프롬프트를 입력하는 것으로 모델 튜닝을 완료할 수 있습니다. 사용자가 플랫폼에게 거는 대화를 기반으로 플랫폼이 자동으로 합성 데이터를 생성하고, 이를 이용하여 모델 튜닝이 이루어지게 됩니다. 파인튠 완료된 모델은 자동 테스트를 거쳐 모델 카드와 함께 사용자에게 다운로드 가능한 형태로 제공됩니다. Finetun.ing은 NVIDIA NemoTron 을 기반으로 구축되어 있으며 Llama 3.1과 Gemma 2부터 지원을 시작할 예정입니다. 현재 다양한 최신 모델의 파인튜닝을 지원하기 위해 테스트를 진행하고 있고, 앞으로 더 많은 모델을 이용할 수 있도록 추가할 예정입니다.

    현재 Finetun.ing은 최종 공개를 앞두고 있고, 이번 행사에서 최초로 Waitlist를 받기로 결정했습니다. https://finetun.ing 에서 Waitlist에 등록하실 수 있습니다.

    Cluster Designer

    Backend.AI Cluster Designer는 GUI 기반의 클러스터 설계 도구입니다. 고객이 원하는 규모와 성능에 맞춰 해당 클러스터의 실효 성능과 함께 필요한 하드웨어 구성과 예상 비용을 자동으로 산출해 줍니다. 실제 구축에 앞서 최적의 아키텍처를 검증하고 싶은 분들께 안성맞춤입니다.

    Helmsman

    Backend.AI Helmsman은 대화형 클러스터 관리 인터페이스입니다. 터미널에서의 채팅만으로 복잡한 클러스터 운용이 가능해집니다. 내부적으로는 Gemma 기반의 파인튠 모델을 활용해, 사용자의 의도를 정확히 파악합니다. TorchTune, LangGraph, LangChain 등의 패키지들을 결합해 온프레미스 환경에서의 대화형 파인튜닝 파이프라인 구축도 지원합니다. Helmsman CLI 및 WebUI를 통한 UI 패키지 및 모델은 Backend.AI 24.09 릴리즈 이후, 연말까지 릴리즈할 예정입니다.

    Acceleration made easy

    두 번째는 "Acceleration made easy"입니다. 우리는 AI 워크로드를 위해 다양한 가속기를 지원하고 있습니다. 현존하는 AI 인프라스트럭처 플랫폼 중 가장 많은 가속기를 지원하고 있습니다.

    CPU 아키텍처 기준으로는 x86은 물론 Arm, RISC-V 등 이종 아키텍처까지 아우릅니다. NVIDIA의 Grace Hopper, AMD의 MI 시리즈, 인텔 Gaudi, GraphCore BOW, GroqCard, Rebellions ATOM+, Furiosa RNGD 등 최신 가속기와의 긴밀한 협업을 통해 Backend.AI 에서 동일한 사용자 경험 및 최고의 성능을 달성할 수 있도록 노력하고 있습니다.

    Inference made easy

    마지막으로 "Inference made easy" 입니다.

    저희는 통합 모델 스토어를 통해 사전학습 모델의 공유와 배포를 간소화했습니다. Hugging Face를 비롯해 자체 레지스트리, 레시피 기반 모델 빌드 등 다양한 방식을 제공하죠. 윈도우의 Choco, macOS의 Homebrew 등의 패키지매니저에서 영감을 얻은 Lablup ION 모델 레시피는 GitHub을 통해 커뮤니티가 기여한 모델 및 서비스들을 명령어 한 줄로 설치할 수 있게 합니다.

    PALI, PALI PALI (PALI2), PALANG

    모델 서비스 운영 측면에서도 새로 소개드릴 내용이 있습니다. 바로 PALI (빨리), PALI2 (빨리 빨리), PALANG (빨랑)입니다.

    PALI(Performant AI Launcher for Inference) 는 Backend.AI 모델 플레이어와 큐레이트 된 모델 카탈로그, 미리 정의된 모델을 조합한 고성능 추론 런타임입니다. 유연한 확장성과 뛰어난 성능이 장점입니다. 누구든지 쉽게 설치하고, NVIDIA NIM, Hugging Face 모델 및 Lablup ION 레시피를 바로 실행하여 모델 서비스를 운영할 수 있습니다.

    PALI2 는 PALI를 위한 전용 하드웨어 인프라 어플라이언스입니다. PALI가 탑재된 어플라이언스 여러 개를 연결해 손쉽게 확장할 수 있습니다. PALI2는 AI 워크로드에 최적화된 아키텍처로, 높은 성능과 낮은 지연 시간을 자랑합니다. 설치 환경에 따라 다양한 아키텍처 및 칩 환경에 맞춘 모델들을 제공하고 업데이트 할 수 있죠.

    NVIDIA 레퍼런스 플랫폼인 GH200을 통합한 PALI2 어플라이언스도 준비하고 있고, 일본 교세라 미라이 엔비전에서 10월 1일에 PALI2의 첫번째 레퍼런스 플랫폼으로 Instant.AI라는 플랫폼을 런칭, 여러분들이 구입할 수 있게 됩니다.

    한국 시장에서의 레퍼런스 플랫폼은 10월 중 예약, 4분기부터 판매 예정에 있습니다. 미국 및 유럽 시장을 대상으로 하는 PALI2 어플라이언스들은 빠르면 올해 4분기부터 만나볼 수 있을 것입니다.

    PALANG은 PALI와 FastTrack, Talkativot, Helmsman 등을 아우르는 언어 모델 추론 플랫폼입니다. 즉시 사용 가능한 추론 및 파인튜닝 세팅을 제공, 대규모 언어 모델의 배포와 운영을 크게 단순화했습니다. Talkativot을 통해서는 맞춤형 챗봇 인터페이스를 쉽게 만들 수 있으며, 개발 과정에서의 모델 비교 및 인터페이스 빌딩을 위한 소프트웨어 컴포넌트들도 제공합니다. 인퍼런스만 필요한 경우 PALI 및 PALI2를 사용하면 되고, 언어모델 파인튜닝과 추론이 모두 필요한 경우 PALANG을 사용할 수 있습니다.

    G

    마지막으로 원 모어 씽... 현재 개발 중인 신규 프로젝트 하나를 살짝 공개하겠습니다. Gemma2 기반의 언어 모델, G입니다. Finetun.ing으로 간편하게 커스터마이징할 수 있는 것이 특징입니다. Helmsman 의 백엔드 모델 및 기업용 에이전트 등의 다양한 용도로 사용될 예정이며 구체적인 내용은 추후에 소개드리도록 하겠습니다.

    From Uncharted AI to Industrial Revolution

    대항해시대, 무수히 많은 모험가들은 후추를 찾아 전세계를 누볐습니다. 그들의 도전은 결과적으로 미지로 남았던 세계의 다양한 영역을 발견했고, 결과적으로 그들이 개척해낸 항로를 통해 세상은 더욱 연결되었습니다. 조선술과 항해술이 발전했고, 새로운 교역로가 열렸고, 의학과 군사기술을 비롯한 다양한 분야에서 혁신이 일어났습니다. 그 뿐만이 아닙니다. 대항해시대가 축발시킨 또 다른 중요한 이벤트, 산업혁명을 빼놓을 수는 없겠죠.

    지금 우리가 마주하고 있는 이 시대를 저희는 대 AI시대라고 부릅니다. 대 AI 시대는 마치 대항해시대 초기와 같이 그 가능성의 문이 이제 막 열리기 시작한 단계에 불과합니다. 누군가는 이제 막 겨우 후추를 들고 돌아오고 있으며, 누군가는 지구가 둥글다는 것을 증명하기 위해 더 큰 배를 건조하고 띄우려 하고 있죠. 이제 AI 분야에도 대항해시대가 산업 혁명에 가져온 변화가 막 일어나고 있습니다.

    Engine of AI Infrastructure

    산업혁명은 제임스 와트의 증기 엔진으로부터 시작되었습니다. 증기 엔진이 발명되면서 대량 생산과 기계화의 시대가 열렸습니다. 이제 우리는 또 다른 혁명의 한가운데 서 있습니다. 거대한 물결 앞에서, 래블업은 새로운 엔진을 만들고 있습니다.

    래블업은 AI 인프라 분야의 엔진입니다. 우리의 기술은 산업 전반에 혁신의 동력을 제공합니다. 증기기관이 석탄의 힘을 이용했다면, 래블업의 엔진은 데이터를 연료로 삼습니다. 마치 자동차 엔진이 휘발유의 에너지를 움직임으로 전환하듯, 래블업은 데이터라는 연료를 인공지능과 인공지능이 주는 가치로 전환하는 효율적이고 강력한 엔진을 제공합니다.

    내연기관이 자동차 산업을 태동시켰듯, AI 엔진은 데이터 기반의 IT산업을 재편할 것입니다. 단순히 데이터를 저장하고 관리하는 수준을 넘어 모든 사람과 기업이 각자 보유한 데이터로부터 통찰과 가치를 이끌어내는 시대, 래블업은 그 시대를 준비하고 있습니다. 래블업의 AI 엔진은 규모와 속도 면에서 타의 추종을 불허합니다. 작게는 IoT, 크게는 수십에서 수만 대에 이르는 GPU를 동시에 가동하고, 페타바이트급 데이터를 실시간으로 처리하는 스케일을 지원합니다. 엔진의 성능이 자동차의 속도를 결정하듯, 우리의 인프라는 AI 생태계에서의 성패를 좌우할 것입니다.

    지금까지 래블업이 만들어온 엔진들을 보셨습니다. 우리는 이 엔진들로 대AI시대를 넘어 AI 산업혁명 시대를 견인해보려 합니다. 여러분 모두가 각자 모두 운전석에 앉을 수 있도록, 우리는 엔진을 설계하고 개선하는 일에 매진하겠습니다. 래블업과 함께 대AI시대의 가속페달을 밟아 주시기 바랍니다.

    27 September 2024

  • FastTrack을 활용한 Domain adaptive language model fine-tuning

    By 권용근

    Introduction

    이번 글에서는 Backend.AI의 MLOps 플랫폼인 FastTrack을 이용하여 공급망(Supply chain) 및 무역 관련 도메인에 특화된 언어모델을 학습시키고, 평가하는 방법에 대해 설명합니다. 해당 언어모델을 위한 Base model로는 공급망 및 무역 도메인 데이터 세트를 통해 continual-pretrained된 gemma-2-2b-it 모델을 사용하였습니다. 사용처에 따라 Question Answering task에 특화된 모델을 학습시키기 위해 웹에서 직접 수집, 가공한 도메인 데이터 세트를 학습 가능한 질문과 답변으로 구성되는 포맷(이하 Q/A task)으로 변환하여 사용하였습니다.

    AI를 개발하는 과정에서는 데이터 전처리, 학습, 검증, 배포, 추론과 같은 단계를 거쳐야 합니다. 래블업의 FastTrack을 사용하면 위와 같은 각각의 단계를 하나의 파이프라인으로 구성할 수 있고, 파이프라인 구성에 따라 특정 단계를 건너뛰거나 단계별 자원량을 다르게 설정하는 등 손쉽게 커스터마이징 할 수 있습니다.

    Concept of Domain Adaptation

    본격적인 모델 학습에 들어가기 앞서, Domain Adaptation이라는 과정이 필요합니다. 생소하신 분들을 위해 짧게 설명하자면, Domain Adaptation이란 사전 학습된 모델을 특정 도메인에 적합하도록 개선하는 프로세스를 말합니다. 오늘날 우리가 접하는 대다수의 일반적인 언어모델들은 특정 분야에 전문적인 지식을 가지도록 만들어지지 않았습니다. 대부분의 모델은 일반적인 도메인에서의 데이터 세트를 사용하여 다음 토큰을 잘 예측할 수 있도록 학습한 뒤, 전반적인 사용 방향에 맞게 fine-tune 되어 만들어집니다. 그러나 전문 도메인에서 사용될 목적으로 모델을 만든다면, 일반적인 데이터 세트를 사용하여 학습시키는 것은 충분치 않습니다. 예를 들어, 범용적인 도메인에서 학습된 모델은 "이 영화가 매우 훌륭했다"와 같은 일반적인 문장의 맥락을 잘 파악할 수 있지만, "법원이 채무자의 자산을 압류하도록 명령했다"와 같은 법률 도메인의 문장은 제대로 해석하지 못할 수 있습니다. 이는 모델이 각 도메인에서 사용되는 특수한 용어와 표현을 학습하지 않았기 때문입니다. 또 다른 예시로, 어떠한 Q/A task가 주어졌다면 일반적인 데이터로는 Q/A task를 구현할 수 없을 가능성이 있습니다. 제대로 된 Q/A task를 처리하기 위해서는 Q/A task에 특화된 데이터 세트로 사전 학습된 언어 모델을 fine-tune하는 식으로 '특정한 도메인의 데이터'를 넣어주어야 하기 때문이죠. 이러한 fine-tuning 과정은 모델이 작업의 뉘앙스를 더 잘 이해하여 사용자의 domain-specific한 질문에 대해 효과적으로 답변할 수 있도록 합니다.

    이번 글에서는 공급망(Supply Chain Management, 이하 SCM) 및 무역 도메인에 특화된 모델을 개발하는 과정을 다룹니다. 위 그림에서 볼 수 있듯이, "영화"나 "여행" 같은 일반 도메인 용어와 "항공화물운송장", "대금결제인"과 같은 SCM 도메인 용어 사이에는 현격한 차이가 있습니다. 이러한 차이를 좁히기 위해 SCM과 무역 도메인에서의 데이터 세트를 활용하여, 해당 도메인에 대한 모델의 이해도를 높이고, 맥락을 더욱 정확하게 파악할 수 있도록 조정하는 것이 우리가 오늘 달성해볼 목표입니다. 정리하면, Domain Adaptation은 본질적으로 서로 다른 도메인 간의 격차를 해소, 새로운 맥락에서 모델이 더 나은 성능을 발휘할 수 있도록 돕는 과정이라고 할 수 있습니다.

    Train model from scratch vs DAPT

    그렇다면 처음부터 해당 도메인의 데이터 세트를 통해 학습(Train model from scratch)하면 되지 않을까요? 물론 가능하지만, 여러가지 한계점이 존재합니다. 만약 처음부터 해당 도메인의 데이터 세트를 통해 학습하게 되면, 해당 도메인에서의 지식은 물론 일반적인 도메인에서의 지식조차 없는 상황이기 때문에 더 많은 데이터 세트와 학습이 요구될 수 있습니다. 일반적인 도메인에서의 딥러닝을 위한 데이터 세트를 수집하는 것도 어렵지만, 특정 도메인에 국한된 양질의 데이터를 수집하는 것은 더욱 어려운 일입니다. 데이터를 수집했다 치더라도, 모델 학습에 맞게 전처리하는 과정에서 많은 시간과 비용이 발생하게 되죠. 따라서 모델을 처음부터 학습시키는 것은 해당 도메인의 데이터 세트를 충분히 확보하고 있고, 자원을 충분히 보유한 기업에 더 적합한 방법이라고 할 수 있습니다.

    만약 domain-adaptive 한 model을 개발하고 싶은데 아주 많은 데이터 세트를 확보하지 못했거나, 자원이 충분하지 않다면 어떻게 해야 할까요? 이런 경우 선택할 수 있는 방법이 Domain-Adaptive Pre-Training (DAPT)입니다. DAPT란, 이미 일반적인 도메인을 통해 충분히 학습된 모델을 특정 도메인의 데이터 세트로 continual pretraining (지속 학습)하여 도메인에 특화된 모델을 개발하는 과정을 말합니다. 이 방법은 일반적인 도메인에 대한 지식을 이미 보유하고 있는 모델을 추가로 학습시키는 방법이기 때문에, 모델을 처음부터 학습시키는 방법에 비해 상대적으로 적은 비용과 데이터 세트를 요구합니다.

    Development environment Setup

    1. 모델 학습에 앞서, 필요한 패키지들을 설치합니다.
    pip install bitsandbytes==0.43.2
    pip install deepspeed==0.14.4
    pip install transformers==4.43.3
    pip install accelerate==0.33.0
    pip install flash-attn==1.0.5
    pip install xforms==0.1.0
    pip install datasets==2.20.0
    pip install wandb
    pip install evaluate==0.4.2
    pip install vertexai==1.60.0
    pip install peft==0.12.0
    pip install tokenizers==0.19.1
    pip install sentencepiece==0.2.0
    pip install trl==0.9.6
    pip install bitsandbytes==0.43.2
    pip install deepspeed==0.14.4
    pip install transformers==4.43.3
    pip install accelerate==0.33.0
    pip install flash-attn==1.0.5
    pip install xforms==0.1.0
    pip install datasets==2.20.0
    pip install wandb
    pip install evaluate==0.4.2
    pip install vertexai==1.60.0
    pip install peft==0.12.0
    pip install tokenizers==0.19.1
    pip install sentencepiece==0.2.0
    pip install trl==0.9.6
    
    1. 모듈 가져오기
    import os
    import json
    from datasets import load_from_disk, Dataset,load_dataset
    import torch
    from transformers import AutoTokenizer, AutoModelForCausalLM, Gemma2ForCausalLM, BitsAndBytesConfig, pipeline, TrainingArguments
    from peft import LoraConfig, get_peft_model
    import transformers
    from trl import SFTTrainer
    from dotenv import load_dotenv
    import wandb
    from huggingface_hub import login
    

    Dataset preparation

    데이터 세트는 fine-tuning의 목적에 따라 다르게 준비되어야 합니다. 이 글에서는 무역 영역에 대한 질문에 효과적으로 답변할 수 있는 모델을 학습하는 것을 목표로 하기 때문에, 웹 크롤링을 통해 자체적으로 수집한 데이터 세트를 사용합하기로 결정했습니다. 데이터 세트는 무역 자격증 시험 데이터 세트, 무역 용어-정의 데이터 세트, 무역 강의 스크립트 데이터 세트의 세 가지 유형으로 분류됩니다.

    1. 무역 자격증 시험 데이터 세트

    질문: 다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야 한다. 정답: 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 질문: ...

    1. 무역 용어 정의 데이터 세트
    {
      "term": "(계약 등을) 완전 무효화하다, 백지화하다, (처음부터) 없었던 것으로 하다(Rescind)",
      "description": "계약을 파기, 무효화, 철회, 취소하는 것; 그렇지 않았음에도 불구하고 계약을 시작부터 무효인 것으로 선언하고 종결짓는 것."
    }
    
    
    1. 무역 강의 스크립트 데이터 세트

    예전에는 전자상거래 셀러가 엑셀에다가 입력을 해서 수출신고 데이터를 업로드 해서 생성을 했잖아요 그리고 대량으로 전송하는 셀러는 api를 통해서 신고를 했습니다 그런데 그 수출신고 정보의 원천정보를 뭐냐면 쇼핑몰에서 제공하는 판매 주문정보입니다 그래서 그 쇼핑몰에 직접 저희가 연계를 해서 판매 주문 정보를 가져올 수 있게끔 새 서비스를 만들었어요 그래서 API 연계된 쇼핑몰들이 있는데 그게 현재 5개가 연결되어 있는데 쇼피 쇼피파이 라자다 라쿠텐 q10이 있고요 아마존하고 위치도 연계 예정에 있습니다 그래서 셀러는 ...

    Q/A 작업에 알맞은 모델을 만들려면 데이터 세트를 질의응답 형식으로 변환해야 합니다. 첫 번째 데이터 세트인 무역 자격증 시험 데이터 세트와 두 번째 데이터 세트인 무역 용어 정의 데이터 세트는 간단한 코드를 사용하여 변환할 수 있지만, 세번째 데이터 세트인 무역 강의 스크립트 데이터 세트를 확인해 보면 대화 형식의 데이터를 직접 변환하기 어려워 보입니다. 이 경우에는 대화형 스크립트에서 대규모 언어 모델(LLM)을 활용하여 Q/A 쌍을 추출하는 접근 방식을 사용할 수 있습니다. 프롬프트와 그 결과는 다음과 같습니다.

    Prompt

    summary: {summary}
    Instruction: 다음 summary를 기반으로 질문과 해당 답변을 추출하세요.
    출력 형식은 JSON으로, 각 질문과 답변을 객체로 묶어 'qa_pairs'라는 배열 안에 넣어야 합니다.
    'questions' key에는 요약에서 도출된 질문 목록이 포함되어야 하며, 'answers' key에는 각 질문에 대한 해당 답변 목록이 포함되어야 합니다:
    출력 예시:
    {{"qa_pairs": [{{"question": "Question1", "answer": "Answer1"}},{{"question": "Question2", "answer": "Answer2"}},{{"question": "Question3", "answer": "Answer3"}},]...}}
    Output:
    

    Output

    {"qa_pairs":
      [
        {"question": "전자상거래 무역 플랫폼은 어떤 서비스를 제공하나요?", "answer": "판매 주문 관리, 창고 관리, 수출입 통관, 배송 관리 등 전자상거래 업무를 위한 다양한 서비스를 제공합니다"},
        {"question": "쇼핑몰과의 API 연계를 통해 무엇을 간소화할 수 있나요?", "answer": "판매 주문 정보를 자동으로 가져와 수출신고를 간소화할 수 있습니다"},
        {"question": "창고 관리 기능은 어떤 것을 관리할 수 있나요?", "answer": "자가 창고, 위촉 창고, 해외 풀필먼트 센터 등을 관리할 수 있습니다"},
        {"question": "수출입 통관 서비스는 어떤 기능을 제공하나요?", "answer": "미국 세관과 연동된 수출입 통관 서비스를 통해 미국 수출 시 수입 신고를 간편하게 처리할 수 있습니다"},
        {"question": "배송 관리 기능은 어떤 서비스를 지원하나요?", "answer": "우체국 EMS, DHL 등 개별 배송과 함께 해외 현지 라스트 마일 배송까지 지원합니다"}
      ]
    }
    

    이제 간단한 코드를 사용하여 각 데이터 세트를 Q/A 데이터 집합으로 변환할 준비가 된 것 같습니다. 아래의 코드를 사용하여 각 데이터 세트를 Q/A 형식으로 변환해 보겠습니다.

    import os
    import json
    import re
    from datasets import Dataset, concatenate_datasets, load_from_disk
    
    def replace_dot_number(text):
        result = re.sub(r'\.(\d+)\.', r'. \1.', text)
        return result
    
    def read_json(path):
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    
    def write_json(data, path):
        with open(path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False)
    
    def dataset_maker(data:list) -> Dataset:
        return Dataset.from_list(data)
    
    def save_dataset(dataset, save_path):
        dataset.save_to_disk(save_path)
    
    def exam_qa_formatter():
        data = []
        root = 'dataset/exam_data'
        for file in sorted(os.listdir(root)):
            file_path = os.path.join(root, file)
            content = read_json(file_path)['fixed_text']
            question_list = content.split('질문:')[1:]
            for question in question_list:
                try:
                    question_and_options = replace_dot_number(question.split('정답:')[0]).strip()
                    answer = question.split('정답:')[1].strip()
                    data.append({"context": replace_dot_number(question), "question":question_and_options, "answer":answer})
    
                except Exception as e:
                    pass
        return data
    
    def description_to_term_formattter(kor_term, eng_term, description):
        context = f"{kor_term}: {description}"
        question = f"설명: '{description}' 이 설명에 해당하는 무역 용어는 무엇인가요?"
        answer = kor_term if eng_term is None else f"{kor_term}, {eng_term}"
        return context, question, answer
    
    def term_to_description(kor_term, eng_term, description):
        context = f"{kor_term}: {description}"
        question = f"'{kor_term}({eng_term})' 이라는 무역 용어는 어떤 의미인가요?" if eng_term is not None else f"'{kor_term}' 이라는 무역 용어는 어떤 의미인가요?"
        answer = description
        return context, question, answer
        
    def term_qa_formatter():
        data = []
        root = 'dataset/term_data'
        for file in os.listdir(root):
            file_path = os.path.join(root, file)
            term_set = read_json(file_path)
            if file == 'terms_data_2.json':
                term_set = [item for sublist in term_set for item in sublist]
            for pair in term_set:
                eng_term = pair.get('eng_term', None)
                if 'term' in pair.keys():
                    kor_term = pair['term']
                else:
                    kor_term = pair['kor_term']
                description = pair['description']
                context_1, question_1, answer_1 = description_to_term_formattter(kor_term, eng_term, description)
                context_2, question_2, answer_2 = term_to_description(kor_term, eng_term, description)
                data_1 = {"context": context_1, "question": question_1, "answer": answer_1} 
                data_2 = {"context": context_2, "question": question_2, "answer": answer_2} 
                data.append(data_1)
                data.append(data_2)
        return data
    
    def transcript_qa_formatter():
        data = []
        root = 'dataset/transcript_data/success'
    
        for file in sorted(os.listdir(root)):
            file_path = os.path.join(root, file)
            for line in open(file_path):
                line = json.loads(line)
                context = line['context']
                output = line['json_output']
    
                qa_pairs = json.loads(output)['qa_pairs']
                for pair in qa_pairs:
                    question = pair['question']
                    answer = pair['answer']
                    if type(answer) == list:
                        answer = answer[0]
                    data.append({"context": context, "question": question, "answer": answer})
        return data
    
    ###### Term dataset
    {'context': 'APEC 경제위원회(Economic Committee (EC)): 개별위원회나 실무그룹이 추진하기 어려운 여러분야에 걸친 이슈에 대한 분석적 연구작업을 수행하기 위해 결성된 APEC 기구,',
     'question': "설명: '개별위원회나 실무그룹이 추진하기 어려운 여러분야에 걸친 이슈에 대한 분석적 연구작업을 수행하기 위해 결성된 APEC 기구,' 이 설명에 해당하는 무역 용어는 무엇인가요?",
     'answer': 'APEC 경제위원회(Economic Committee (EC))'}
    
    ###### Transcript dataset
    {'context': '수입 신고는 일반적으로 입항 후에 하는 것이 원칙이며, 보세 구역에서 5부 10장을 작성하여 신고합니다',
     'question': '수입 신고는 언제 하는 것이 원칙인가요?',
     'answer': '수입 신고는 일반적으로 입항 후에 하는 것이 원칙입니다.'}
    
    ###### Exam dataset
    {'context': ' 다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야  한다.정답: 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다.',
     'question': '다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야  한다.',
     'answer': '대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다.'}
    
    # Exam dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 1430
    })
    
    # Term dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 15678
    })
    
    # Transcript dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 8885
    })
    
    # Concatenated dataset 
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 25993
    })
    

    Q/A 형식의 데이터 세트와 합쳐진 데이터 세트(학습 데이터 세트)는 위와 같습니다. 약 26,000개의 Q/A 쌍이 학습에 사용될 것으로 예상됩니다.

    이제 fine-tuning을 위한 데이터 세트가 준비되었습니다. 이 데이터 세트가 실제로 모델에 어떻게 입력되는지 확인해 보겠습니다.

    <bos><start_of_turn>user
    Write a hello world program<end_of_turn>
    <start_of_turn>model
    

    huggingface 웹사이트에서는 채팅 템플릿 형식과 모델의 프롬프트 형식 정의에 대한 정보가 포함된 gemma-2b-it의 모델 카드를 찾을 수 있습니다(gemma-2-2b-it). 즉, gemma에게 질문을 하려면 모델이 이해할 수 있는 형식의 프롬프트를 만들어야 하는 것이죠.

    대화의 시작은 <start_of_turn>으로 표시되며, 대화의 끝은 <end_of_turn>으로 표시됩니다. 화자는 사용자 및 모델로 지정되어 있음을 알 수 있습니다. 따라서 모델에게 질문을 할 때 프롬프트의 형식은 위와 같은 형식이어야 합니다.

    def formatting_func(example):
        prompt_list = []
        for i in range(len(example['question'])):
            prompt_list.append("""<bos><start_of_turn>user
        다음 질문에 대답해주세요:
        {}<end_of_turn>
        <start_of_turn>model
        {}<end_of_turn><eos>""".format(example['question'][i], example['answer'][i]))
            return prompt_list  
    

    이 문서에서는 Q/A 데이터 세트를 사용하여 모델을 학습시키는 데 중점을 두고 있으므로 '이런 종류의 질문에는 이렇게 대답해야 한다'는 식으로 접근하여 모델을 학습시킬 것입니다. 앞서 언급한 채팅 템플릿을 고려하면 위와 같은 형식으로 코드를 작성할 수 있습니다. 이때, 채팅 템플릿에 토큰이 명시적으로 포함되어 있지 않더라도 모델은 구분 기호 이상으로 더 많은 콘텐츠를 생성하려고 시도할 수 있습니다. 이 때 모델이 답변만 제공하고 턴을 종료하도록 하기 위해 토큰을 추가해줍니다.

    <bos><start_of_turn>user
    다음 질문에 대답해주세요:
    '(관세)감축률(Reduction Rate)' 이라는 무역 용어는 어떤 의미인가요?<end_of_turn>
    <start_of_turn>model
    관세를 감축하는 정도를 말함. 예를 들어 200%p에 관세감축률이 50%를 적용하면 감축 후 관세는 100%p가 됨. 극단적인 경우로 관세감축률이 100%이면 모든 관세는 감축 후에는 0%p가 됨.<end_of_turn><eos>
    

    실제 학습에서는 위와 같은 예시가 input으로 들어가게 됩니다. 이제 학습을 위한 데이터 세트 준비를 마쳤습니다.

    Training

    학습 코드는 아주 간단합니다. SFTTrainer를 사용하며, base model로는 이전에 SCM & 무역 데이터 세트를 통해 continual-pretrained 된 모델을 gemma-2-2b-it 모델을 사용합니다.

    model_id = "google/gemma-2-2b-it"
    output_dir = 'QA_finetune/gemma-2-2b-it-lora128'
    tokenizer = AutoTokenizer.from_pretrained(model_id, token=access_token)
    
    model = AutoModelForCausalLM.from_pretrained(
                # "google/gemma-2-2b-it",
                "yonggeun/gemma-2-2b-it-lora128-merged",
                device_map="auto",
                torch_dtype=torch.bfloat16,
                token=access_token,
                attn_implementation="eager", # attn_implementation,
                cache_dir="./models/models",
            )
    
    
    def formatting_func(example):
        prompt_list = []
        for i in range(len(example['question'])):
            prompt_list.append("""<bos><start_of_turn>user
    다음 질문에 대답해주세요:
    {}<end_of_turn>
    <start_of_turn>model
    {}<end_of_turn><eos>""".format(example['question'][i], example['answer'][i]))
        return prompt_list   
    
    
    def train(data):  
        valid_set = data["test"]
        valid_set.save_to_disk('QA_finetune/valid_set/gemma-2-2b-it-lora128')
    
        lora_config = LoraConfig(
            r=256,
            lora_alpha=32,
            lora_dropout=0.05,
            bias="none",
            target_modules=["q_proj", "o_proj", "k_proj", "v_proj", "gate_proj", "up_proj", "down_proj"],
            task_type="CAUSAL_LM",
        )
    
        training_args = TrainingArguments(
            per_device_train_batch_size=2,
            warmup_steps=2,
            logging_steps=1, 
            gradient_accumulation_steps=4,
            # num_train_epochs=3,
            num_train_epochs=3,  
            learning_rate=2e-4,
            save_steps=100,
            fp16=False,
            bf16=True,
            output_dir=output_dir,
            push_to_hub=True,
            report_to="wandb"
        )
    
        trainer = SFTTrainer(
            model=model,
            tokenizer=tokenizer,
            train_dataset=data['train'],
            args=training_args,
            formatting_func=formatting_func,
            peft_config=lora_config,
            max_seq_length=max_length,
            packing= False,
        )
    
        model.config.use_cache = False
    
        print("Training...")
        trainer.train()
        print("Training done!")
    

    Evaluation

    훈련이 성공적으로 완료되면 필수적으로 모델의 성능을 평가해야 합니다. 이 글에서는 특정 도메인에서의 Question Answering 성능을 평가하는 데 중점을 두었기 때문에 일반적인 모델의 벤치마크에 사용되는 것과는 다른 지표가 필요했습니다. 이 글에서는 SemScore와 Truthfulness를 활용하여 모델을 평가했습니다.

    SemScore: 대상 응답과 모델 응답 간의 의미적 텍스트 유사성을 기반으로 하는 평가 방법입니다.(SemScore)

    Evaluating Truthfulness: 이 방법은 LLM에 모델 응답과 답변을 제공한 다음 1에서 5까지의 척도로 진실성을 측정하는 방식입니다.(Truthfulness)

    Fasttrack pipeline

    이제 FastTrack에서 모델 학습에 사용될 파이프라인을 만들어보겠습니다. 파이프라인은 FastTrack에서 사용하는 작업 단위입니다. 각 파이프라인은 최소 실행 단위인 태스크(Task)의 묶음으로 표현될 수 있습니다. 하나의 파이프라인에 포함되는 여러 개의 태스크는 서로 의존 관계를 가질 수 있으며, 이 의존성에 따라 순차적으로 실행이 보장됩니다.

    Create Pipeline

    위 그림에서 파란색 '+' 버튼을 찾아 새 파이프라인을 만듭니다.

    파이프라인을 생성하면 파이프라인의 이름과 설명, 사용할 데이터 저장소의 위치, 그리고 파이프라인에서 공통적으로 적용할 환경변수 등을 선택할 수 있습니다. 필요한 정보를 입력한 후 하단의 "Save" 버튼을 클릭하여 파이프라인을 생성합니다.

    Drag and create task

    새 파이프라인이 만들어지면 작업 템플릿에 새 작업을 추가할 수 있습니다. Custom Task를 클릭해서 아래 작업 공간으로 끌고오면, task가 새롭게 생성됩니다.

    Enter information

    작업을 만들 때는 위와 같이 작업 실행에 필요한 정보를 입력해야 합니다. 태스크 이름과 설명을 이해하기 쉽게 작성하고, 단일 노드 또는 다중 노드 중 하나를 선택합니다. 본 문서에서는 단일 노드 훈련을 수행하므로 단일 노드를 선택하겠습니다.

    다음으로 명령어를 작성해야 합니다. 명령은 기본적으로 세션을 실행하는 명령어입니다. 실행할 스크립트가 오류 없이 작동할 수 있도록 마운트된 V-folder의 디렉터리를 정확하게 지정해야 합니다. 학습에 필요한 대부분의 패키지는 이미 세션에 설치되어 있지만, 추가 패키지를 설치해야 하거나 버전 문제가 있는 경우에는 패키지를 다시 설치해야 할 수 있습니다. 이 경우 requirements.txt 파일에서 필요한 패키지를 지정하고 설치 후 다른 스크립트를 실행할 수 있습니다.

    Resource configuration

    다음은 세션, 리소스 및 V-folder 설정입니다.

    본 글에서는 Pytorch 기반으로 코드를 작성하였지만, Pytorch 이외에도 Tensorflow, Triton server 등의 환경을 선택할 수 있습니다.

    FastTrack의 장점 중 하나는 리소스를 최대한 효율적으로 활용할 수 있다는 점입니다. 하나의 리소스 그룹 내에서도 여러 세션에 리소스를 분할하여 resource utilization rate를 극대화할 수 있습니다.

    데이터 세트 준비의 경우 별도의 GPU 연산이 필요하지 않기 때문에 GPU 리소스를 할당하지 않아도 괜찮습니다. 이를 통해 최소한의 리소스로 코드를 실행할 수 있으며, 이 시간 동안 다른 세션에 GPU 리소스를 할당할 수 있어 GPU 리소스가 유휴 상태로 남는 상황을 방지할 수 있습니다. 또한 병렬적으로 모델 학습이 필요한 경우(예: 10FGPU가 가용하고 각 훈련 세션에 5FGPU씩 필요한 경우) 모델을 병렬로 학습시킬 수 있죠. 이렇게 하면 리소스 낭비를 줄이고 학습 시간을 단축할 수 있습니다.

    준비한 데이터 세트와 학습 코드가 작성되어 있는 V-folder를 올바르게 선택합니다.

    Duplicate or delete task

    작업 블록의 우측 상단 미트볼 메뉴 아이콘 (⋯)을 누르면 생성된 작업을 복제하거나 삭제할 수 있습니다.

    FastTrack에서는 이와 같이 생성된 여러 개의 작업 사이 순서를 정할 수 있습니다. 이는 작업들 간의 의존성을 추가하는 과정입니다. 경우에 따라 여러개의 작업이 끝난 뒤에 다음 작업이 실행되도록 설정할 수도 있습니다. 이 경우에는 작업들 간의 의존성에 따라 모든 작업이 끝나기 전까지 다음 작업이 진행되지 않습니다. 완성된 예시는 위와 같습니다. 이번 글에서는 dataset preparation - fine-tuning - evaluation 순서로 작업을 진행합니다.

    각 작업이 올바르게 정의되었다면 'Run'을 클릭하여 파이프라인을 실행합니다.

    FastTrack 화면 좌측에서 생성했던 파이프라인들을 확인할 수 있습니다. 클릭하면 파이프라인 작업 세션에서 현재 실행 중인 작업 및 실행했던 작업들을 모니터링 할 수 있습니다.

    Monitoring jobs

    위와 같은 화면을 통해 작업을 모니터링 할 수 있습니다. 각 작업은 지정된 순서대로 진행되며, 이전 작업이 완료되면 다음 작업을 위한 세션을 시작하기 위해 리소스가 할당되고, 작업이 완료되면 세션이 종료됩니다. 필요한 경우 작업을 건너뛸 수 있는 옵션도 있습니다. 일례로, 위의 이미지에서는 dataset preparation 작업을 건너뛰고 fine-tuning 작업이 실행되고 있는 것을 볼 수 있습니다. 건너뛴 작업은 분홍색, 실행되고 있는 작업은 하늘색, 실행 예정인 작업은 노란색으로 나타납니다.

    Log checking

    빨간 네모로 강조되어 있는, 각 작업의 이름 옆의 파란색 버튼을 클릭하면 각 작업의 로그를 확인할 수 있습니다. 이를 통해 트레이닝 진행 상황을 직접 모니터링할 수 있습니다. 로그는 터미널과 동일한 결과로, 위의 화면과 같이 나타납니다. 학습이 잘 이뤄지고 있는 것을 확인할 수 있죠. 파이프라인 실행이 성공적으로 완료되면 결과를 확인할 수 있습니다. 본 문서에서는 평가 결과를 플로팅하여 /home/work/XaaS/train/QA_finetune/truthfulness_result.png 로 저장하도록 구성했습니다. (Backend.AI의 V-folder는 /home/work/~ 가 기본 디렉토리 구조입니다.)

    학습을 마친 뒤, 해당 경로에 결과 이미지가 생성된 모습입니다.

    Result checking

    위와 같이 파이프라인이 성공적으로 실행된 모습을 작업 이름의 왼쪽에서 확인할 수 있습니다.

    Result

    이제 모델을 Fine-tuning 한 결과를 gemma-2-2b-it와 비교하여 확인해보겠습니다.

    1. SemScore (목표 응답과 모델 응답 간의 의미론적 텍스트 유사성, 1.00 is the best)

    | Base Model | Trained Model | |------------|---------------| | 0.62 | 0.77 |

    학습된 모델의 SemScore가 증가했습니다(0.62 -> 0.77). 이 결과는 학습된 모델이 목표 응답과 의미적으로 더 유사한 출력을 생성할 수 있음을 나타냅니다. 즉, 학습된 모델이 의도한 목표 응답에 더 가깝고 의미적으로 더 일관된 응답을 생성하는 능력이 향상되었다는 것입니다. 결과적으로 학습된 모델의 전반적인 성능과 신뢰성이 크게 향상되었다고 할 수 있습니다.

    1. Truthfulness 학습된 모델은 고득점 사례는 증가하고 저득점 사례는 감소하는 경향을 보입니다. 낮은 점수(1, 2점) (1,111 -> 777), 높은 점수(4, 5점) (108 -> 376) 이는 모델이 진실에 가까운 도메인 정보를 식별하는 능력이 향상되고, 훈련이 효과적이었다는 것을 나타냅니다.

    Truthfulness result

    Conclusion

    이번 글에서는 Backend.AI의 MLOps 플랫폼인 FastTrack을 활용하여 특정 domain에 특화된 모델을 학습하는 Pipeline을 구축해보았습니다. FastTrack의 모든 기능을 활용하지 않고 일부 기능만을 사용했음에도 자원을 유연하게 활용하고, Task 설정을 자유롭게 하여 학습 시간을 단축하고, 자원 활용율을 끌어올릴 수 있었습니다. 또한 독립적인 실행 환경에서 안정적으로 학습을 시킬 수 있었으며, Pipeline Job의 실행 정보를 모니터링할 수 있어 학습이 진행되는 동안 각 파이프라인의 자원 사용량 및 실행 횟수를 파악할 수 있었습니다. 이 글에서 다룬 내용 이외에도 FastTrack은 스케줄링, 병렬 모델 학습과 같은 추가적인 기능들을 다양하게 지원하고 있습니다. 아래 첨부된 래블업 블로그 게시글에서 각각 강지현님과 강정석님이 작성하신 FastTrack의 다른 기능들에 대한 더 많은 정보를 확인할 수 있습니다.

    FastTrack의 모든 기능을 활용하지는 못했지만, 자원의 유연한 활용, 자유로운 task 설정 등을 통해 학습 시간을 단축하고 자원의 utilization rate를 높일 수 있었습니다. 또한 독립적인 실행환경에서 안정적인 학습이 가능하고, Pipeline Job 실행 정보를 통해 각 파이프라인에서의 자원 사용량, 실행 횟수 등을 파악할 수 있었습니다. 이외에도 FastTrack에서는 스케줄링, 병렬 모델 학습 등 많은 기능을 지원합니다. 아래의 문서들에서 FastTrack에 대한 더 많은 정보를 확인할 수 있습니다.

    Backend.AI MLOps 플랫폼 FastTrack을 소개합니다.

    FastTrack 길라잡이: 모델 학습 결과 알림 받기

    26 September 2024

  • Model Variant: 손쉽게 대접하는 다양한 모델 서비스

    By 강지현

    들어가며

    어떠한 연구 목적으로 AI를 학습시켜 결과물을 만들어내야 하는 상황에 있다고 가정해봅시다. 우리가 해야 할 일은 AI에게 가르쳐 준 데이터를 AI가 올바르게 학습하길 기다리는 것뿐이죠. 하지만 AI를 '활용'하는 어떠한 서비스를 만든다고 가정하면 이야기가 복잡해집니다. 다양한 모델을 어떻게 시스템에 적용시킬 것인지, 부하 상황에서 어떤 기준에 의해 스케일링을 시켜야 할지 모든 요소 하나하나가 고민거리죠. 이런 고민에 대한 답을 얻기 위해 함부로 사용자가 존재하는 프로덕션 환경을 수정할 수도 없습니다. 프로덕션 환경을 늘렸다 줄였다 하다가 사고라도 난다면 끔찍한 일이 생길 수도 있거든요. 만약에 끔찍한 일이 벌어졌다면, 벌어진 일을 수습하기 위한 시간이 필요할 텐데, 우리 서비스를 사용하는 소비자에게는 모델 학습을 기다리는 연구자와 같은 참을성을 기대할 수 없을 겁니다. 엔지니어링 영역의 어려움 외에 비용에 대한 어려움도 있습니다. 모델을 서비스하는데에는 당연히 비용이 들고, 모델을 학습시키는 그 순간에도 자원을 소모하고 있는 만큼 사용자가 비용을 지출하고 있는 셈이니까요. 그러나 걱정하실 필요는 없습니다. 이미 세상에는 잘 만들어진 모델들이 많이 존재하고, 우리는 그러한 모델들을 가져다가 서비스하는 것으로 충분한 경우가 많거든요. 저희 솔루션에 관심이 있으셨던 분들이라면 다 아시는 내용이겠지만, Backend.AI는 이미 여러분이 모델을 서비스할 때 필요로 하는 기능들을 다양하게 지원하고 있습니다. 트래픽에 따라 서비스를 늘리는 것이나 줄이는 것도, 사용자의 입맛에 맞춘 다양한 모델을 서비스하는 것도 가능하죠.

    그러나 여기서 멈출 Backend.AI 팀이 아닙니다. 저희는 Backend.AI의 23.09 버전부터 제공된 모델 서비스를 한 층 강화하였고, 다양한 모델을 손쉽게 서비스할 수 있도록 개선하였습니다. 이번 포스팅을 통해 어떤 방법으로 쉽고 간편하게 다양한 모델을 서비스할 수 있는지 알아봅니다.

    이번 포스팅에서는 다양한 종류의 모델을 더욱 간편하게 서비스할 수 있는 기능을 소개합니다. 모델 서비스에 대한 설명은 23.09 버전 업데이트를 릴리즈하며 한 차례 드린 적이 있기 때문에, 자세한 설명은 생략하겠습니다. Backend.AI의 모델 서비스가 생소하시다면, 다음 포스팅을 먼저 읽어보시는 것을 추천합니다. Backend.AI Model Service 미리 보기

    기존 방식

    | | 필요조건 | 기존 방식 | 모델 배리언트(Model Variant) | |---|----------|------------|----------------------------------| | 1 | 모델 정의 파일(model-definitionl.yaml) 작성 | O | X | | 2 | 모델 정의 파일을 모델 폴더에 업로드 | O | X | | 3 | 모델 메타데이터 필요 | O | △* (일부는 자체 다운로드 가능) |

    Backend.AI 모델 서비스는 실행하기 위한 모델 메타데이터 외에 모델을 서비스할 때 실행할 명령어를 일정한 형식으로 담아둔 모델 정의 파일 (model-definition.yaml)을 필요로 했습니다. 서비스를 실행하는 순서는 다음과 같습니다. 모델 정의 파일을 작성하고, 모델 정의 파일을 읽을 수 있도록 모델(model) 타입 폴더에 업로드한 뒤, 모델서비스 시작시 모델 폴더를 마운트하면 자동으로 모델 정의 파일에 따라 엔드유저의 입력을 받아 모델로 전달하고, 응답 값을 보내주는 API 서버 등이 실행되는 형태였습니다. 하지만 이 방식은 모델 정의 파일을 수정할 때마다 파일에 접근해야한다는 단점이 있었습니다. 또, 이미 모델 정의 파일에 모델 경로가 정해져있기 때문에 모델이 달라질 때마다 모델 정의 파일을 다르게 작성해야 하는 것도 귀찮은 부분이었습니다. 이번에 선보이는 모델 배리언트(Model Variant)는 모델 정의 파일이 없이 모델 메타데이터만을 가지고 몇 가지 설정값을 입력하거나, 또는 아예 입력할 필요없이 즉시 모델을 서비스할 수 있는 기능입니다. 모델 배리언트에서는 커맨드(command), vLLM, 그리고 NIM(NVIDIA Inference Microservice) 방식을 지원합니다. 서비스하는 방법과 모델 서비스 실행을 확인하는 방법은 다음과 같습니다.

    이번에 선보이는 모델 배리언트(Model Variant)는 모델 정의 파일이 없이 모델 메타데이터만을 가지고 몇가지 설정값을 입력하거나, 또는 아예 입력할 필요없이 즉시 모델을 서비스 할 수 있는 기능입니다. 모델 배리언트에서는 커맨드(command) 방식, vLLM 방식, 그리고 NIM(NVIDIA Inference Microservice) 방식을 지원합니다. 서비스하는 방법과 모델 서비스 실행을 확인하는 방법은 다음과 같습니다.

    기본적으로, 모델 서비스는 서빙할 모델 메타데이터를 필요로 합니다. 가장 손쉽게 접할 수 있는 모델 메타데이터를 받을 수 있는 Hugging Face 에서 서비스할 모델을 다운로드 받아보세요. 이번 예제에서는 Hugging Face 의 Llama-2-7b-hf 모델과 Calm3-22b-chat 모델을 사용했습니다. 모델 메타데이터를 모델 폴더에 업로드 하는 방법은 앞의 포스팅의 모델 스토리지 준비를 참고하십시오.

    빌드된 이미지에서 자동으로 모델 서비스하기 (command 방식)

    첫 번째로 소개하는 커맨드 방식은 모델 정의 파일에서 모델을 서비스하기 위해 실행하는 명령어 부분이 실행 이미지에 들어간 형태입니다. CMD 라는 환경변수에 실행할 명령어를 지정한 뒤, 이미지를 빌드해 실제 모델을 서비스할 때 다른 입력 없이 바로 실행하는 방식이죠. 커맨드 방식은 서비스가 제대로 실행되고 있는지 확인하는, 이른바 Health check를 지원하지 않습니다. 따라서 대규모의 서비스를 수행할 때보다는 프로토타입으로 바로 서비스를 띄워서 확인해 볼 때 적절합니다. 실행방법은 다음과 같습니다.

    1. 시작화면에서 서비스할 모델 서비스에 해당하는 모델 메타데이터가 들어있는 모델 폴더를 마운트하도록 Model Storage To Mount 항목에서 Llama-2-7b-hf 를 선택하고, Inference Runtime Variant 항목에서 Predefined Image Command 를 선택합니다.

    모델 서비스를 별도의 토큰없이 접근할 수 있도록 제공할 경우 Open To Public 스위치 버튼을 활성화 해주세요.

    모델-서비스-시작화면-모델-메타데이터-마운트-및-CMD-선택

    1. 서비스할 환경을 선택합니다. 여기서는 vllm:0.5.0 를 사용하고, 자원은 CPU 4 Core, Memory 16 GiB, NVIDIA CUDA GPU 10 FGPU 를 할당하도록 설정했습니다.

    모델-서비스-시작화면-실행환경-선택-및-자원할당

    1. 마지막으로 클러스터 크기를 선택하고, 시작버튼을 클릭합니다. 클러스터 크기는 싱글노드, 싱글 컨테이너로 설정했습니다.

    모델-서비스-시작-화면-클러스터-크기-선택-및-시작

    서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 되고 엔드포인트 주소가 나오게 됩니다.

    모델-서비스-상세-화면

    서비스 확인하기

    서비스가 정상적으로 띄워졌다면, cURL 명령어로 서비스 모델명을 우선 확인합니다.

    curl https://cmd-model-service.asia03.app.backend.ai/v1/models \
    -H "Content-Type: application/json"
    

    모델명-확인하기

    이제 서비스에 보낼 입력을 cURL 명령어로 보내고, 응답값을 확인해보겠습니다.

    CMD로 실행하는 모델 서비스는 이미지에 이미 모델명이 정의되어 있기 때문에 모델명을 확인후 요청을 보낼 때 모델명을 model 키의 값으로 입력해야 합니다.

    curl https://cmd-model-service.asia03.app.backend.ai/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
    "model": "image-model",
    "prompt": "San Francisco is a",
    "max_tokens": 7,
    "temperature": 0}'
    

    모델-서비스-요청-결과-화면

    vLLM 모드로 모델 서비스하기

    vLLM 모드는 앞에서 소개한 커맨드 방식과 비슷하지만, vLLM 을 실행할 때 입력하는 여러가지 옵션들을 환경변수로 작성할 수 있습니다. 실행방법은 다음과 같습니다.

    실행방법

    1. 시작화면에서 서비스할 모델 서비스에 모델 폴더를 마운트하고, Inference Runtime Variant 항목에서 vLLM 을 선택합니다.

    모델-서비스-시작-화면-모델-메타데이터-마운트-및-vLLM-선택

    1. 서비스할 환경을 선택합니다. 앞서 설명한 커맨드 방식과 동일하게 vllm:0.5.0 으로 선택하고, (자원은 동일하게 설정해도 되지만) 이번에는 CPU 16 Core, Memory 64 GiB, NVIDIA CUDA GPU 10 fGPU를 할당하도록 하겠습니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    1. 마지막으로 클러스터 크기를 선택하고 환경 변수 BACKEND_MODEL_NAME 을 입력합니다. 이 값은 vLLM에서 --model-name 옵션에 대응하는 값으로, 사용자가 서비스에 요청을 보낼 때 지정하는 model 값이 됩니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    마찬가지로 서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 되고, 서비스가 띄워진 엔드포인트 주소가 나오게 됩니다.

    모델-서비스-상세-화면

    서비스 확인하기

    서비스에 보낼 입력을 cURL 명령어로 보내고, 응답값을 확인해보겠습니다. 이 때 model 값은 아까 설정한 BACKEND_MODEL_NAME 값으로 입력합니다. 입력이 끝났다면 START 버튼을 클릭해서 서비스를 생성합니다.

    curl https://vllm-calm3-22b-chat.asia03.app.backend.ai/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
    "model": "vllm-model",
    "prompt": "初めて会う日本人ビジネスマンに渡す最高の挨拶は何でしょうか?",
    "max_tokens":  200,
    "temperature": 0
    }'
    

    모델-서비스-요청-결과-화면

    NIM 모드로 모델 서비스하기

    NIM 을 실행하기 위해서는 NGC의 NIM 모델 레지스트리에 접근할 수 있는 계정으로부터 발행된 API 키가 있어야 합니다. 키값을 얻는 방법은 다음 내용을 참고하시기 바랍니다. NVIDIA Docs Hub : How to get NGC API Key

    NIM(NVIDIA Inference Microservice) 모드 역시 커맨드 모드와 유사하나, NVIDIA의 NIM을 지원하는 모델 서버가 내장된 이미지로 실행해야 합니다. 또, 모델을 불러올 때에, NGC API 키 값이 필요합니다. 모든 것이 준비되었다는 가정하에 모델 서비스를 시작해보겠습니다.

    실행방법

    1. 시작화면에서 서비스할 NIM 에서 받아올 메타데이터를 캐싱할 비어있는 모델 타입 폴더를 선택하고, Inference Runtime Variant 항목에서 NIM 을 선택합니다.

    모델-서비스-시작-화면-모델-폴더-마운트-및-NIM-선택

    1. 서비스할 환경을 선택합니다. 여기서는 ngc-nim:1.0.0-llama3.8b 를 사용하고, 자원은 CPU 8 Core, Memory 32 GiB, NVIDIA CUDA GPU 15 FGPU 를 할당하도록 설정했습니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    1. 마지막으로 클러스터 크기를 선택하고 환경 변수 HF_HOME으로 기본 경로인 /models 경로를 입력합니다. 그리고 NGC_API_KEY 을 입력하고, 발급받은 키값을 입력합니다. 입력이 끝났다면 CREATE 버튼을 클릭해서 서비스를 생성합니다.

    모델-서비스-시작-화면-클러스터-크기-선택-환경변수-입력-및-시작

    NIM 을 사용할 경우 모델 메타데이터를 저장소로부터 받아오기 때문에 처음 실행시에는 다소 시간이 소요될 수 있습니다. 세션 페이지에서 서비스중인 라우팅 세션에 대한 컨테이너 로그를 확인하여 진행상황을 확인할 수 있습니다. 모델-서비스에-대응하는-라우팅-세션 NIM-에서-데이터를-받고-있는-로그가-띄워진-컨테이너-로그-화면

    커맨드, vLLM 모드와 같이 서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 됩니다. 서비스가 띄워진 엔드포인트 주소를 활용해 서비스에 보낼 내용을 다음과 같이 입력하고, 응답값을 확인해보겠습니다.

    서비스 확인하기

    from openai import OpenAI
    
    client = OpenAI(
      base_url = "https://nim-model-service.asia03.app.backend.ai/v1",
      api_key = "$YOUR_NGC_API_KEY"
    )
    
    completion = client.chat.completions.create(
      model="meta/llama3-8b-instruct",
      messages=[
          {        
            "role":"user", 
            "content":"Hello! How are you?"
          },
          {
            "role":"assistant",
            "content":"Hi! I am quite well, how can I help you today?"
          },
          {
            "role":"user",
            "content":"Can you write me a song?"
          }],
      temperature=0.5,
      top_p=1,
      max_tokens=1024,
      stream=True
    )
    
    for chunk in completion:
      if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")
    

    모델-서비스-요청-결과-화면

    마치며

    모델 배리언트 기능은 이미 학습된 모델로 실질적인 서비스를 제공하는 것을 목표로 하는 연구자와 기업에 많은 도움이 될 것입니다. 강력한 자원 관리 시스템과 NVIDIA GPU, AMD ROCm, TPU, Graphcore IPU, Furiosa Warboy, Rebellions ATOM, Hyperaccel LPU 등과 같이 다양한 AI 가속기 지원을 바탕으로 한 Backend.AI 는 이제 단순히 모델을 학습하는 것을 뛰어넘어 서비스까지 쉽게 배포할 수 있는 통합 환경을 제공하게 되었습니다. Backend.AI 와 함께 여러분이 원하는 AI 모델을 언제든 서비스해보세요.

    11 July 2024

  • Backend.AI 오픈소스 기여 가이드 (2024년 7월)

    By 성대현

    Backend.AI의 코어 엔진은 많은 오픈소스 소프트웨어를 활용함과 동시에 그 자체도 오픈소스로 개발되고 있습니다. 오픈소스로 개발되는 만큼, 버그를 찾았거나 불편함을 느낀 사용자가 있다면 개인이 직접 Backend.AI 프로젝트에 기여하는 것 또한 가능하죠. (물론, Backend.AI를 이용하시는 엔터프라이즈 고객분들께는 저희의 고객 및 기술 지원 채널을 통해 이슈가 생길 경우 지원을 해드리고 있답니다.)

    기여를 하기 위한 방법에는 두 가지가 있는데요, 첫번째 방법은 어떤 문제가 있는지, 어떤 개선 아이디어가 있는지 상세하게 개발팀에게 설명을 남기는 'issue'이고, 두번째 방법은 직접 코드를 수정하여 기여할 수 있는 'pull request' 입니다.

    Backend.AI 오픈소스 기여 가이드 글을 통해 래블업의 개발팀과 더욱 효과적이고, 빠른 의사소통을 위해 알아두면 좋은 내용을 소개합니다.

    GitHub 저장소 소개

    이전의 글 Backend.AI 오픈소스 기여 가이드에서 보듯 Backend.AI은 원래 Backend.AI meta-repository와 여러 하위 컴포넌트들로 저장소를 구분하여 개발되었습니다.

    그러나, Backend.AI의 "22.06"버전부터는 Pants를 이용한 mono-repository 방식으로 변경되었습니다.

    이와 같은 개발 워크플로의 전환으로 다수의 개별 컴포넌트에서 종종 발생하는 패키지 호환성 문제를 해결해 더욱 편리한 개발 환경을 구성하는 데에 많은 도움이 되었습니다.

    Pants는 빠르고, 확장성이 있으며, 사용자 친화적인 빌드 시스템입니다.

    우선, 이슈를 올리고 싶다면 가장 먼저 살펴보실 곳은 Backend.AI repository입니다. Backend.AI라는 프로젝트 이름을 가진 저장소는 Pants를 이용하여 통해 여러 패키지를 통합 설치하고 있습니다. 이 저장소는 프로젝트 관리뿐만 아니라 실제로 어떤 기능을 하는 코드가 들어가는 저장소입니다. Backend.AI의 서버 및 Client SDK 관련 이슈들은 모두 여기서 관리되고 있으며, README를 통해 다른 프로젝트로의 링크를 제공합니다.

    이슈를 새로 생성할 때 기본 템플릿으로는 bug report와 feature request 2가지 양식을 제공하고 있으나, 이 양식을 꼭 엄격하게 따라야만 하는 것은 아닙니다. 다만 Backend.AI의 복잡도나 다양한 사용 환경을 고려하였을 때 해당 양식에 맞춰서 내용을 작성해주시면 문제 파악을 위한 맥락 공유가 조금 더 쉬워진다는 점을 고려해주십시오.

    Mono-repository에 대한 소개

    Backend.AI는 버전 "22.06"부터 Backend.AI는 Pants를 이용한 mono-repository로 변경하였습니다. Mono-repository는 여러 프로젝트의 기본 종속성, 데이터 모델, 기능, 툴링 및 프로세스를 공유하는 소스코드를 가지고 통합한 코드 베이스의 프로젝트입니다. 이전에 사용하던 여러 프로젝트를 하나의 프로젝트로 통합하여 저장소를 운영하고 있습니다.

    Pants 소개

    Backend.AI는 Pants를 이용한 빌드시스템으로 설치합니다. Pants에 대한 자세한 내용은 다음의 링크 Pants - Getting started를 확인하시기 바랍니다.

    Backend.AI의 컴포넌트 관계

    그림 1. Backend.AI 주요 컴포넌트 사이의 관계 구조

    그림 1은 Backend.AI의 주요 컴포넌트 관계를 나타낸 다이어그램입니다.

    그림 2. Backend.AI의 주요 컴포넌트 구조도 및 실행 방법의 예

    그림 2는 Backend.AI의 주요 컴포넌트 구조를 나타낸 다이어그램이며, 컴포넌트의 소스코드 위치 및 실행 명령등을 보여주고 있습니다.

    Backend.AI의 대다수 컴포넌트는 Backend.AI repository에서 관리되며, 소스코드는 src/ai/backend/ 하위 디렉토리에 위치하여 있습니다. 간략하게, 컴포넌트별로 하는 일에 대해 디렉토리별로 요약하면 다음과 같습니다:

    • src/ai/backend/manager (Manager): 전체 클러스터의 연산자원 모니터링 및 세션 스케줄링을 담당하고 사용자 인증 및 세션 실행 등의 API를 제공하는 핵심 서비스
    • src/ai/backend/agent (Agent): 연산노드에 설치되어 컨테이너들을 관리 및 제어하는 서비스
    • src/ai/backend/common (Common): 여러 서버 측 컴포넌트에서 공통으로 또는 자주 사용되는 기능 및 데이터 형식을 모아놓은 라이브러리
    • src/ai/backend/client (Client SDK for Python): 공식 명령 줄 인터페이스(CLI)이자 Python을 위한 API wrapper 함수·클래스들을 제공하는 라이브러리
    • src/ai/backend/storage (Storage Proxy): 사용자 웹 브라우저 또는 Client SDK가 네트워크 스토리지로부터의 대용량 입출력을 바로 할 수 있도록 해주는 서비스
    • src/ai/backend/web (Web Server): Web UI와 SPA (single-page app) 구현을 위한 라우팅을 제공하고 웹 세션 기반 사용자 인증을 제공하는 HTTP 서비스
    • src/ai/backend/webui (Web UI & Desktop App): 실제 사용자가 접하는 UI의 웹 컴포넌트 기반 구현체. Electron 기반 데스크톱 앱 빌드도 지원. 또한, 사용자가 컨테이너 내부에서 실행 중인 애플리케이션 포트로 바로 접속할 수 있도록 해주는 app proxy의 로컬 경량화 버전도 포함.

    Backend.AI의 버전 관리 방법

    Backend.AI는 6개월(매년 3월과 9월)마다 주요 릴리즈가 이뤄지며, 릴리즈 후 사후 지원을 약 1년간 제공합니다. 따라서 버전 번호는 YY.0M.micro 방식의 CalVer 형식을 따르고 있습니다 (예: 20.09.14, 21.03.8). 다만 Python 패키징 시스템의 버전 번호 정규화 때문에 wheel 패키지의 버전은 월 부분에 zero-padding이 없는 YY.MM.micro 형식입니다 (예: 20.9.14, 21.3.8). 버전 업데이트 주기가 본체 릴리즈 주기와 다른 세부 컴포넌트들은 일반 SemVer 형식을 따르고 있는 예도 있습니다.

    개발 전 우선 설치해야 하는 필수 패키지

    Backend.AI를 설치하기 전에 먼저 Docker, Docker Compose v2 등을 설치해야 합니다. Backend.AI는 repository의 scripts/install-dev.sh 스크립트로 설치 시 Docker, Docker Compose v2 등의 설치 여부를 검사하여 설치 방법을 안내합니다. 만약, Python, pyenv, Docker, npm이 설치되지 않았으면 아래와 같이 필수 패키지 설치를 해야합니다. Python의 경우는 시스템 패키지의 Python3로 설치하시기 바랍니다. 이후, pyenvpyenv-virtualenv를 설치해야 합니다.

    $ curl https://pyenv.run | bash
    

    이후 Docker와 Docker Compose v2를 다음과 같이 설치하면 됩니다.

    MacOS

    MacOS의 경우는 Docker Desktop on Mac으로 설치하면 Docker와 Docker Compose v2가 자동으로 설치됩니다.

    Ubuntu, Debian, CentOS, Fedora Core등 Linux 환경

    Ubuntu, Debian, CentOS, Fedora Core의 경우 다음의 스크립트를 이용하면 Docker와 Docker Compose v2가 자동으로 설치됩니다.

    $ sudo curl -fsSL https://get.docker.io | bash
    

    Docker 설치한 후, 만약 sudo 없이 실행하였을때 다음과 같이 unix:///var/run/docker.sock 접근 권한 오류가 생기는 이슈가 있습니다.

    $ docker ps
    Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied
    

    위와 같은 권한 문제가 존재하는 경우 아래와 같이 명령어를 이용하여 권한을 설정합니다.

    $ sudo usermod -aG docker $(whoami)
    $ sudo chown root:docker /var/run/docker.sock
    

    이후, 재부팅을 한 후, docker run hello-world 를 실행하여, 정상 실행되는걸 확인하면 됩니다.

    $ docker run hello-world
    Unable to find image 'hello-world:latest' locally
    latest: Pulling from library/hello-world
    c1ec31eb5944: Pull complete
    Digest: sha256:94323f3e5e09a8b9515d74337010375a456c909543e1ff1538f5116d38ab3989
    Status: Downloaded newer image for hello-world:latest
    
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    
    To generate this message, Docker took the following steps:
    1. The Docker client contacted the Docker daemon.
    2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
        (amd64)
    3. The Docker daemon created a new container from that image which runs the
        executable that produces the output you are currently reading.
    4. The Docker daemon streamed that output to the Docker client, which sent it
        to your terminal.
    
    To try something more ambitious, you can run an Ubuntu container with:
    $ docker run -it ubuntu bash
    
    Share images, automate workflows, and more with a free Docker ID:
    https://hub.docker.com/
    
    For more examples and ideas, visit:
    https://docs.docker.com/get-started/
    

    chown 으로 /var/run/docker.sock의 group ownership 변경이 아닌 /var/run/docker.sock 파일의 권한을 666으로 변경하여 그룹내 다른 사용자도 접근 가능하게 변경하면 재부팅을 하지 않아도 됩니다.

    sudo chmod 666 /var/run/docker.sock
    

    그러나, /var/run/docker.sock 파일의 권한을 666으로 설정하면, 보안 취약점이 생깁니다.

    Docker Compose v2 설치 여부는 다음과 같이 확인해봅니다.

    $ sudo docker compose version
    Docker Compose version v2.28.1
    

    만약, nvm이 설치되어있지 않다면 nvm을 다음의 링크 nvm - install & Update Script에 나온 것처럼 설치해야 합니다.

    $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
    

    nvm 설치 이후에는 최신 LTS 버전의 Node.js을 설치 하고 사용 설정을 하면 됩니다.

    $ nvm install --lts
    $ nvm use --lts
    

    개발 환경 설치 방법

    실제로 코드를 통해 기여하기 위해서는 pull request를 작성해야 하는데, 단순한 오타 수정 혹은 문서 기여가 아니라면 코드를 수정하며 직접 결과물을 돌려서 확인해봐야 하기 때문에 개발 환경을 구축하는 절차가 꼭 필요합니다. Backend.AI는 여러 개의 컴포넌트가 함께 맞물려 돌아가는 구조로, 하나의 저장소를 clone하고 Python 가상환경을 만들어 editable install[1]을 해주는 것만으로는 설치가 끝나지 않습니다. 최소한 manager, agent, storage-proxy, webserver, wsproxy를 모두 설정 및 실행해야만 동작하는 GUI를 확인할 수 있으며 CLI 환경을 위해서는 여기에 client SDK도 별도로 설치해야 합니다. 또한 manager 구동 및 agent와의 통신을 위한 Redis, PostgreSQL, etcd 서버도 함께 실행해야 합니다.

    앞서 소개한 필수 패키지를 설치했고, Backend.AI의 여러 컴포넌트를 설치하려면 repository의 scripts/install-dev.sh 스크립트로 설치하면 됩니다. 이 스크립트가 하는 일은 다음과 같습니다:

    • pyenv, Python, Docker, npm 등의 설치 여부를 검사하여 설치 방법을 안내
    • 위와 같은 다양한 컴포넌트들을 모두 각자의 디렉토리에 설치
      • 이때 accelerator-cuda와 같이 다른 컴포넌트의 동작에 필요한 컴포넌트들은 editable 상태로 추가 설치됩니다.
    • 각 컴포넌트가 서로 바라볼 수 있는 기본 포트 설정 및 예제 인증키 등을 포함한 database/etcd fixture 추가
    • PostgreSQL, Redis, etcd 서비스를 "halfstack"이라는 이름으로 Docker Compose를 이용해 생성 및 실행

    install-dev 스크립트 실행이 성공적으로 완료되면, manager, agent 등의 서비스 데몬을 실행하기 위한 명령어 및 기본 설정된 예제 계정 정보를 출력합니다. 설명을 따라 tmux, screen 등의 터미널 멀티플렉서 또는 터미널 앱의 다중 탭 기능 등을 활용하여 각각 독립된 shell에서 서비스 데몬들을 실행하고, hello world 예제까지 동작하는 것을 확인하면 Backend.AI를 개발 및 테스트할 수 있는 준비가 된 것입니다.

    현재 이 방법은 Intel (amd64/x86_64) 및 ARM 기반 macOS 및 Ubuntu/Debian/CentOS/Fedora 및 Docker Compose가 설치되는 배포판의 Linux 환경만 지원합니다.

    보통 처음 이 install-dev 스크립트를 이용하면 도중에 다양한 오류나 사전 검사 실패로 인해 중단하고 다시 실행해야 하는 경우가 자주 발생합니다. 이때는 scripts/delete-dev.sh 스크립트를 활용하면 삭제 절차를 간편하게 수행할 수 있습니다.

    Backend.AI 설치 및 삭제하기

    이 install-dev 및 delete-dev 스크립트를 활용하면, Backend.AI를 자유롭게 설치하고, 삭제할 수 있습니다. 먼저 Backend.AI 저장소를 복제합니다.

    $ git clone https://github.com/lablup/backend.ai 
    

    위의 Backend.AI를 설치합니다.

    $ cd backend.ai
    $ ./scripts/install-dev.sh 
    

    설치가 완료되면, 화면에 나오는 결과 내용을 숙지하시기 바랍니다.

    만약, Backend.AI를 삭제하려면 Backend.AI 저장소를 복제한 위치에서 scripts/delete-dev.sh 스크립트를 실행하면 됩니다.

    $ cd backend.ai
    $ ./scripts/delete-dev.sh 
    

    컨트리뷰션 전에 알아야 할 사항

    대부분의 분산 버전 관리 시스템에서 관리되는 프로젝트와 마찬가지로, Backend.AI 에 기여하기 위해서는 원본 원격 저장소의 main 브랜치의 가장 최신 커밋 기준으로 코드 작업이 이뤄져야 하며, 충돌이 발생하는 경우에는 리뷰를 요청하기 전에 해결되어야 합니다. 원본 저장소를 fork 한 경우, 현재 본인이 fork 한 원본 저장소와 실제 원본 저장소가 동기화되어야 합니다.

    방법 안내 전 이해를 돕기 위해 아래 정리한 명칭을 참고해주세요.

    • 원본 원격 저장소(upstream): Backend.AI 원본 저장소. 모든 주요 커밋 내용이 반영됨.
    • fork 한 원본 저장소(origin): GitHub을 통해 "내" 계정으로 복사해온 Backend.AI 저장소. (주의: 원본 원격 저장소 != fork 한 원본 저장소)
    • 코드 복사본(local working copy): 현재 본인의 로컬 머신에 내려 받은 fork 된 저장소

    Git 명령의 브랜치 표기

    • main: 현재 local working copy의 main 브랜치
    • origin/main: 내가 local working copy를 만들기 위해 clone을 수행해온 저장소(origin)의 main 브랜치
    • upstream/main: 별도로 추가한 upstream 원격 저장소에 속한 main 브랜치

    작업 흐름 개념

    • fork 하는 시점에 origin/main 이 만들어짐
    • fork 한 저장소를 clone하면 내 작업 컴퓨터에 main 이 만들어짐
    • main 으로부터 새로운 topic branch를 만들어 작업 진행
    • 이 작업 branch를 origin에 올리고 PR을 생성하면 GitHub이 알아서 fork 의 원본 저장소를 가리키도록 해줌
    • 이때, 원본 저장소의 main 이 변경된 것을 작업 도중 동기화해오려면 아래의 절차를 따름

    동기화 하는 방법은 다음과 같습니다.

    • step1: upstream 이라는 이름으로 원본 원격 저장소 추가하기
    $ git remote add upstream https://github.com/lablup/backend.ai
    
    • step2: 원본 원격저장소의 main 브랜치의 최신 커밋을 코드 복사본(local working copy)으로 가져오기
    $ git fetch upstream
    
    • step3: 원본 원격저장소의 main 브랜치 최신 커밋 반영 내역을 origin(본인이 fork 한 원본 저장소의 코드 복사본(local working copy))로 가져오기
    $ git switch main && git merge --ff upstream/main
    
    • step4: step 1 ~ 3에서 진행된 코드 복사본(local working copy)의 변경 내역을 origin(본인이 fork 한 원본 저장소의 원격 저장소)에 반영하기
    $ git push origin main
    

    이제 upstream/mainorigin/mainmain을 거쳐 동기화된 것입니다.

    • step5: 작업 중인 내 브랜치에 최신 업데이트 반영하기
    $ git switch topic
    $ git merge main
    

    이 과정을 수행할 때 origin/mainupstream/main 간에 history 분기가 생긴 상태에서 5번 절차를 잘못 수행하면 굉장히 복구하기 까다로워질 수 있습니다. 또한, Backend.AI에서 사용하는 CI 도구들이 PR을 테스트할 때 upstream/mainorigin/topic 사이의 차이점을 보기 위해 공통 조상 커밋을 찾게 되어 있는데 topic 브랜치를 main 이름을 재활용하는 경우 그러한 도구들이 제대로 동작하지 않게 됩니다. 가능하면 새로운 분기를 만들 때는 항상 새로운 이름을 붙여준다고 생각하면 됩니다.

    Pull Request 작성 요령

    실제 특정 버그 패치나 기능 구현 사항을 PR로 보내려면 먼저 이를 GitHub에 올려야 합니다. 여러 방법이 있지만, 다음과 같은 방법을 권장합니다:

    • GitHub의 저장소 페이지에서 fork 를 뜹니다. (직접 커밋 권한이 있는 경우라면 fork 없이 바로 브랜치를 만드는 것을 권장합니다.)
    • 코드 복사본(local working copy)에서 git remote로 해당 fork 저장소를 가리키게 합니다.
      • 이때, 관례를 따라 래블업의 원본 저장소를 upstream으로, fork해서 새로 만든 저장소를 origin이라고 이름을 붙이면 좋습니다.
      • fork 후 처음 clone하는 경우가 아니라 install-dev로 설치를 먼저 했던 경우라면 원본 저장소가 origin일 것이므로 remote 이름 변경 작업을 해줘야 합니다.
    • 새 브랜치를 만듭니다.
      • 브랜치 이름은 버그 수정인 경우 fix/를, 기능 추가나 개선인 경우 feature/를 앞에 붙여 kebab-case 방식으로 주제를 요약하여 짓습니다. (예: feature/additional-cluster-env-vars, fix/memory-leak-in-stats) 그 외에 docs/, refactor/ 같은 prefix를 사용하기도 합니다.
      • main 브랜치에 직접 수정하여 PR을 작성하는 것도 가능하지만, PR 리뷰 및 수정 기간 동안 main 브랜치에 추가 변경사항이 생기는 경우 upstream 저장소와 동기화할 때마다 매번 rebase 또는 merge해줘야 하기 때문에 더 귀찮습니다. 별도의 브랜치를 따두면 내가 원할 때 rebase 및 merge 를 할 수 있습니다.
    • 변경사항을 해당 브랜치로 커밋합니다.
      • 커밋 메시지는 가급적 conventional commit 스타일을 따릅니다. 브랜치 이름과 마찬가지로 fix:, feat:, refactor:, docs:, release:와 같은 제목 접두어들을 사용하며, Backend.AI 한정으로 의존성 관련 커밋에는 setup:, gitignore 업데이트나 저장소 디렉토리 구조 변경과 같은 경우에는 repo: 같은 추가 접두어를 사용하기도 합니다. 괄호를 묶어 영향받는 컴포넌트를 표기하기도 합니다. (예: fix(scripts/install-dev): Update for v21.03 release)
      • 커밋 메시지는 영어로 작성해야 합니다.
    • 브랜치를 push하고 PR을 작성합니다.
      • 별도 이슈가 있는 PR의 경우 PR 본문에 해당 이슈 번호를 적어주어야 합니다. 만약, 저장소의 이슈를 참조하려면, 다음의 이슈 링크의 https://github.com/lablup/backend.ai/issues/401 숫자를 보고, #401과 같은 형식으로 적으면 GitHub이 자동 링크를 걸어줍니다.
      • PR 본문에 특정한 형식을 요구하지는 않지만, 어떤 문제를 해결하기 위한 것인지, 어떤 원리로 작성하였는지 혹은 어떤 도구나 라이브러리를 활용하였는지, 그러한 선택을 한 이유는 무엇인지 등을 적어주면 좋습니다.
      • PR 제목 및 본문은 영어 또는 한국어로 작성 가능합니다.
      • PR을 생성하면 다양한 자동화된 검사 도구가 동작하는 것을 볼 수 있습니다. 특히, CLA (contributor license agreement)는 반드시 서명(GitHub 사용자이름 등록)해주셔야만 리뷰가 진행됩니다.
      • 각 언어별 기본 코딩스타일·코딩 규칙 검사를 모두 통과해야 합니다. (Python 코드의 경우 flake8, mypy 등)
      • changes 디렉토리가 존재하고 towncrier 검사가 있는 저장소에서는, PR을 생성하여 그 번호를 받으면 changes/<PR번호>.<수정유형> 이름의 파일을 생성하여 Markdown 문법으로 변경사항 내용 요약을 한 줄의 영어 문장으로 작성합니다. (비교적 간단한 내용이거나 기존 이슈가 따로 있는 경우에는 이 내용이 PR 본문 역할을 대신하기도 합니다.) 수정 유형은 fix, feature, breaking, misc, deprecation, doc이 있으며 프로젝트별로 다른 부분은 각 저장소의 pyproject.toml에 정의됩니다. 기존 메시지들을 어떻게 적었는지는 CHANGELOG.md 또는 CHANGES.md와 같은 파일을 참고하면 됩니다.
    • 리뷰 과정을 진행합니다.
      • 완료되면 보통 squash-merge 형태로 리뷰어가 커밋 로그를 정리하여 하나의 단일 커밋으로 만들어 병합하게 됩니다.
      • 따라서 리뷰 과정에서 자잘한 수정 커밋을 자주 만드는 것에 부담을 가지지 않고 자유롭게 생각날 때마다 커밋을 만들어주시면 됩니다.

    GitHub CLI, SourceTree, GitKraken과 같은 도구들을 git 명령어와 함께 활용하면 더욱 좋습니다.

    정리

    지금까지 Backend.AI의 전체적인 컴포넌트 구조와 저장소 구조, 개발환경 설치 방법, 그리고 pull request 작성 요령을 살펴보았습니다. 이 가이드가 Backend.AI 소스코드에 한 발짝 더 다가갈 수 있도록 도움이 되었으면 좋겠습니다.


    [1]: "editable" 설치란 Python 패키지를 소스 디렉토리를 직접 바라보도록 설치하여 site-packages 디렉토리 내부를 편집하지 않고 소스 디렉토리를 수정하는 것만으로도 해당 패키지를 import 시 변경 내용이 바로 반영되어 있도록 하는 설치 방법을 말합니다.

    10 July 2024

  • FastTrack 길라잡이: 모델 학습 결과 알림 받기

    By 강정석

    이제는 클래식이 되어버린 AlexNet부터 오늘날 뜨거운 관심을 받고 있는 여러 거대 언어 모델(이하 LLM)들까지, 우리는 필요에 맞게 다양한 모델을 학습하고 평가합니다. 그러나 현실적으로 모델을 여러 번 실행해 보고 경험이 쌓이기 전까지 우리는 학습이 언제 종료될지 가늠하기 어렵습니다.

    Backend.AI의 뛰어난 스케줄링은 GPU의 유휴 시간을 최소화하고 우리가 잠든 사이에도 모델 학습이 실행될 수 있도록 하였습니다. 그렇다면 더 나아가서, 우리가 잠든 사이에 학습이 완료된 모델의 결과를 전달받을 수 있다면 어떨까요? 이번 글에서는 FastTrack의 신기능과 Slack을 활용하여 모델 학습 결과를 메시지로 수신하는 방법을 다뤄보도록 하겠습니다.

    이 글은 Backend.AI FastTrack 24.03.3 버전을 기준으로 작성되었습니다.

    들어가기에 앞서

    본문은 Slack App 및 Bot을 생성하는 방법을 다루지 않습니다. 자세한 내용은 공식 문서를 참고하는 것을 권장합니다.

    파이프라인 생성하기

    모델 학습에 사용할 파이프라인(Pipeline)을 만들어 보도록 하겠습니다. 파이프라인은 FastTrack에서 사용하는 작업 단위입니다. 각 파이프라인은 최소 실행 단위인 태스크(Task)의 묶음으로 표현될 수 있습니다. 하나의 파이프라인에 포함되는 여러 개의 태스크는 서로 의존 관계를 가질 수 있으며, 이 의존성에 따라 순차적으로 실행됨이 보장됩니다. 각 태스크마다 자원 할당량을 설정할 수 있어 전체 자원을 유연하게 관리할 수 있습니다.

    파이프라인에 실행 명령이 전달되면 해당 시점의 상태를 그대로 복제하여 실행되는데, 이러한 단위를 파이프라인 잡(Pipeline Job)이라고 합니다. 하나의 파이프라인에서 여러 개의 파이프라인 잡이 실행될 수 있으며, 하나의 파이프라인 잡은 하나의 파이프라인으로부터 생성됩니다.

    파이프라인 생성 버튼

    파이프라인 목록 상단에 위치한 파이프라인 생성 버튼("+")을 클릭합니다.

    파이프라인 생성하기

    파이프라인의 이름과 설명, 사용할 데이터 저장소의 위치, 그리고 파이프라인에서 공통적으로 적용할 환경변수와 파이프라인 초기화 방법 등을 선택할 수 있습니다. slack-pipeline-0이라는 이름을 입력한 후 하단의 "Create" 버튼을 클릭하여 파이프라인을 생성합니다.

    태스크 생성하기

    태스크 끌어오기

    새 파이프라인이 생성된 것을 볼 수 있습니다. 이제 태스크를 추가해 보도록 하겠습니다. 상단의 태스크 템플릿 목록(Task templates)에 있는 "Custom Task" 블럭을 마우스로 끌어와서 하단 작업 공간에 놓습니다.

    태스크가 수행할 동작 입력하기

    우측에 태스크의 세부사항을 입력할 수 있는 작업창이 나타납니다. model-training-task라는 이름을 주어 태스크의 역할을 나타낼 수 있으며, 모델 학습을 진행하기 위하여 pytorch:1.11-py38-cuda11.3 이미지를 사용하도록 설정합니다. 실제 모델 학습은 오랜 시간을 소요하므로 이번 예시에서는 아래와 같이 간단한 명령을 수행하도록 합니다.

    # 3초 동안 동작을 중지시킴으로써 실행 시간이 증가합니다.
    sleep 3
    # 파이프라인 전용 폴더에 `result.txt` 파일을 생성합니다. 학습이 완료된 모델의 정확도라고 가정합니다.
    echo "0.$RANDOM" > /pipeline/outputs/result.txt
    

    태스크 생성하기 (1)

    마지막으로 태스크에 할당할 자원량을 입력한 후 하단의 "Save" 버튼을 클릭하여 태스크를 생성합니다.

    또다른 태스크 끌어오기

    모델 학습 태스크가 작업 공간에 생성된 것을 확인할 수 있습니다. 이번에는 앞에서 저장한 result.txt 파일로부터 수치를 읽어와 Slack으로 알림을 보내는 태스크를 만들기 위하여 다시 "Custom Task" 블럭을 하단 작업 공간에 가져옵니다.

    태스크 단위 환경변수 `SLACK_TOKEN` 입력하기

    이번 태스크는 slack-alarm-task라고 이름을 설정하고, 아래와 같은 스크립트를 입력하여 Slack에 알림을 보내는 동작을 수행하도록 합니다.

    pip install slack-sdk
    python -c '
    import os
    from pathlib import Path
    from slack_sdk import WebClient
    SLACK_BOT_TOKEN = os.environ.get("SLACK_TOKEN")
    JOB_ID = os.environ.get("BACKENDAI_PIPELINE_JOB_ID")
    def main():
        result = Path("/pipeline/input1/result.txt").read_text()
        client = WebClient(token=SLACK_BOT_TOKEN)
        client.chat_postMessage(
            channel="#notification",
            text="Pipeline job({}) finished with accuracy {}".format(JOB_ID, result),
        )
    if __name__ == "__main__":
        main()
    '
    

    위 코드는 SLACK_TOKEN, BACKENDAI_PIPELINE_JOB_ID라는 이름의 두 환경변수를 활용하고 있습니다. BACKENDAI_* 형태의 환경변수는 Backend.AI 및 FastTrack 시스템에서 자동으로 추가하는 값들로, 그중 BACKENDAI_PIPELINE_JOB_ID는 각 태스크가 실행되고 있는 파이프라인 잡의 고유 식별자를 나타냅니다.

    또 하나의 환경변수인 SLACK_TOKEN는 태스크 단위 환경 변수로 추가된 값으로, 이 기능을 활용하면 코드 변경 없이 다양한 값을 관리 및 변경할 수 있습니다.

    태스크 생성하기 (2)

    slack-alarm-task 태스크에도 알맞은 자원을 할당해 준 후 하단의 "Save" 버튼을 클릭하여 태스크를 생성합니다.

    태스크 의존성 추가하기

    태스크 의존성 추가하기

    이제 작업 공간에는 두 개의 태스크(model-training-taskslack-alarm-task)가 존재합니다. 이때 slack-alarm-taskmodel-training-task이 종료된 후 실행되어야 하므로 두 태스크 간 의존성을 추가해야 합니다. 먼저 실행되어야 할 태스크(model-training-task)의 하단에서 나중에 실행되어야 할 태스크(slack-alarm-task)의 상단까지 마우스를 끌어다 놓습니다.

    파이프라인 실행하기

    파이프라인 실행하기 (1)

    의존성이 추가되어 model-training-task에서 slack-alarm-task 방향으로 뻗는 화살표가 연결된 것을 볼 수 있습니다. 이제 파이프라인을 실행하기 위하여 우측 상단의 "Run" 버튼을 클릭합니다.

    파이프라인 실행하기 (2)

    파이프라인을 실행하기에 앞서 파이프라인의 간단한 요약을 검토할 수 있습니다. 2개의 태스크가 존재하는 것을 다시 한번 확인한 후 하단의 "Run" 버튼을 클릭합니다.

    파이프라인 실행하기 (3)

    파이프라인이 성공적으로 실행되어 파이프라인 잡이 생성되었습니다. 하단의 "OK"를 클릭하면 파이프라인 잡의 정보를 볼 수 있습니다.

    파이프라인 잡

    파이프라인 잡이 정상적으로 생성되었습니다. 모델 학습(model-training-task)이 완료된 후 slack-alarm-task가 실행 중인 것을 확인할 수 있습니다.

    Slack 알림받기

    Slack 알림 (1)

    Slack 알림 (2)

    파이프라인 잡 실행 결과가 Slack을 통해 사용자에게 전달된 것을 확인할 수 있습니다. 이제 우리는 편한 마음으로 잠들 수 있게 되었습니다.

    30 May 2024

  • 24.03: 출시 업데이트

    By 래블업 주식회사

    2024년 Backend.AI 의 첫 릴리즈인 24.03 이 출시되었습니다. 이번 업데이트에서는 UI 및 사용자 경험이 크게 개선되어 설치와 운영에 대한 기능성이 더욱 향상되었습니다. 지난 23.09 이후 다음과 같은 업데이트 내용을 담고 있습니다.

    Backend.AI Core & WebUI

    • TUI 기반 설치 프로그램이 추가되어 Backend.AI 설치가 한결 간편해졌습니다. 이 설치 프로그램은 다운로드 프로세스를 자동화하여 사용자가 Backend.AI를 쉽게 설치하고 시작할 수 있게 해줍니다.

    • vfolder 에 휴지통 기능이 추가되었습니다. 이제 사용하지 않는 vFolder 의 파일들은 완전히 삭제하는 대신 휴지통으로 이동하고 이후 휴지통 비우기 명령을 통해 완전히 삭제됩니다. 이와 관련하여 vfolder 의 상태를 나타내는 인자값이 추가되었습니다.
    • Backend.AI 모델스토어가 추가되었습니다. 이제 모델스토어에서 다양한 머신러닝 및 딥러닝 모델들을 보관 관리하고 검색하여 쉽게 활용이 가능합니다.
    • vfolder 에 색인용 메타데이터를 추가하여 쿼리시 전체 디렉토리 스캔 대신 인덱스를 활용합니다.
    • 대기 중인 세션 수와 요청된 리소스 슬롯에 기반한 세션 생성에 대한 제한 정책을 도입하여 시스템 리소스 활용이 향상되었습니다. 이 새로운 리소스 정책 옵션은 세션 런쳐의 리소스 프리셋과 커스텀 리소스 슬라이더의 최대값을 필터링하고 설정하는 데 도움이 됩니다.
    • WebUI 에 다크 테마가 추가되었습니다. 이제 사용자들은 개인 취향에 맞는 다양한 옵션의 선택이 가능합니다.

    • WebUI 의 정렬되지 않은 줄바꿈, 공백, 밖으로 새어 나온 안내문 등의 화면 조정과 세션명 유효성 검증 같은 안정성을 위한 기능들이 개선되었습니다.
    • 모델 서빙용 세션 런처에서도 할당 가능한 자원만큼만 입력 가능하도록 UI 입력이 제한됩니다.
    • 다양한 UI 사용자 옵션을 위해 config.toml 파일에 WebUI 앱 다운로드 패널을 숨기는 allowAppDownloadPanel 인자를 추가하였습니다.

    Backend.AI 는 하루가 다르게 변하는 AI 생태계에서 다양한 환경을 지원함과 동시에 더욱 강력하고 사용자 친화적인 환경을 제공하고자 끊임없이 발전하고 있습니다. 앞으로의 행보에도 많은 기대 부탁드립니다! Make your AI accessible with Backend.AI!

    29 March 2024

  • 래블업 2023 겨울 인턴 후기

    By 김병조

    개요

    OpenUp에서 주최한 오픈소스 컨트리뷰션 아카데미(이하 컨트리뷰션 아카데미)에 지원하여 래블업 주식회사(이하 래블업)에서 11월부터 12월까지 8주간 가을 인턴을 진행했으며, 이후 1월부터 2월까지 8주를 추가 연장하여 총 16주간 근무했다.

    군대를 전역하고, 개발자로서 첫 회사인 래블업을 다니면서 경험했던 과정들을 작성했다.

    지원동기

    컨트리뷰션 아카데미 전에도 래블업에 대해서 관심을 가지고 있었는데, 마침 컨트리뷰션 아카데미를 통해서 기여할 수 있는 기회가 생겼다.

    컨트리뷰션 아카데미 기간 동안에 Backend.AI의 webui 이슈를 해결하고 리팩토링을 진행했었다.

    컨트리뷰션 아카데미를 참여하는 동안 Backend.AI에 대한 애정과 관심, 그리고 재미를 많이 느꼈고, 프로그램이 끝나고 계속해서 기여 하고싶다는 생각을 하게되었다.

    마침 컨트리뷰션 아카데미와 연계로 래블업에서 근무할 수 있는 기회가 생겼고, 망설임 없이 지원하게 되었다.

    온보딩

    인턴이 시작되고 첫 3주동안은 온보딩 과정을 진행했다.

    RealTime Chat을 구현, Backend.AI 환경 구성, Pebble Seminar 순서로 진행했다.

    RealTime Chat

    Backend.AI의 Core쪽 코드에 친숙해기지 위한 첫 과제였다. 실시간 채팅 앱을 Python을 이용하여 구현하였으며 aiohttp, aioredis, aysncio 라이브러리를 사용하였다.

    채팅의 내용을 저장해야한다는 조건이 없어 InMemoryDB인 redis를 사용했다.

    채팅방에 입장하면 사용자는 채팅방에 subscribe가 되도록 했으며, 사용자가 메세지를 입력하면 subscribe된 유저 즉 같은 채팅방에 있는 다른 유저들에게 입력한 메세지를 publish하도록 구현했다.

    RealTime Chat 동작 화면

    코딩테스트를 준비하면서 Python을 기본적으로 다룰 수는 있었지만, aiohttp, asyncio, aioredis 등 과 같은 라이브러라를 사용해본 경험이 없어서 개념을 익히고 이해하는데 시간이 걸렸다.

    하지만 해당 과제를 통해서 Backend.AI의 Core쪽 코드들을 이해하는데 많은 도움이 되었으며, 새로운 라이브러리들을 공부할 수 있어서 좋았다.

    Backend.AI 환경 구성

    컨트리뷰션 아카데미에서 이미 Backend.AI를 설치 해본 경험이 있기 때문에, 인턴 기간에 다시 환경 구성하는 과정이 어렵지는 않았다.

    하지만 컨트리뷰션 아카데미에서 설치를 시도하면서 나 또한 많은 에러와 실패를 겪으면서 Backend.AI 설치가 쉽지않다는 것을 알고있었고, 같이 인턴십을 진행하시는 다른 분도 설치 과정에서 어려움을 많이 겪으셨다.

    이미 한번 그 실패들을 경험하고, 해결법을 알고있는지라, 도움을 드릴 수 있었고, 빨리 설치하고 다른 업무를 진행할 수 있었다.

    환경을 구성하면서 가상 머신과 VPN도 설정했고, 로컬에서 문제가 생겨도 업무를 할 수 있도록 가상 머신에도 환경을 구성했다. 이렇게 가상 머신에 구성을 하고나서 이후 업무를 하는 동안 Local은 개발용으로 많이 사용했고, 가상 머신은 테스트 서버를 구성하여 사용했다. 회사에 가상 머신을 쉽게 관리하고 구성할 수 있는 VM Farm이 있어, 개발과 테스트 환경을 구성하기 너무 좋았다.

    Pebble Seminar

    RealTime Chat과 Backend.AI 환경 구성을 마친 후, Backend.AI의 구조와 코드를 이해하고, 그 내용을 바탕으로 짧은 세미나를 준비했다. 나는 Backend.AI WebUI에서 사용돠는 GraphQL과 Relay에 대해서 발표를 하게 되었다.

    GraphQL은 사용해본 경험이 있으나 다른 사람들 앞에서 발표하기에는 가지고 있는 지식이 부족했고 Relay는 처음들어본 라이브러리였기 때문에, Pebble Seminar를 준비하는데 있어 많이 걱정되고, 많은 문서들을 읽으면서 준비했다. 우선 GraphQL과 Relay 공식 문서들을 읽으면서 개념을 익혔고, Backend.AI 코드들을 하나씩 분석하면서 Backend.AI에서는 어떻게 적용이되었고, 어떻게 동작하는지에 대해서 파악했다.

    Pebble Seminar 준비 자료

    Pebble Seminar를 준비하면서 코드륵 분석하다보니, WebUI에서 동작하고 있는 코드들을 자연스럽게 이해하게되었고, 이후 이슈들을 해결하는 과정에서 조금 더 쉽게 문제가 발생하는 코드를 찾고, 해결하는데 큰 도움이 되었다.

    Backend.AI 이슈 해결 및 기능 구현

    온보딩을 마치고, 드디어 프론트엔드 팀에 합류하여 Backend.AI 이슈를 해결하고 기능 구현을 시작했다. 나는 프론트엔드 리더분과 커피챗을 가지며 이번 인턴십 기간 업무 카테고리를 정했다.

    1. Table Column Setting 컴포넌트 제작
    2. E2E Test 관련 조사
    3. 데일리 업무

    11월부터 12월까지 8주간 진행된 인턴십 기간에서 총 19개의 Pull Request를 작성했고, 그 중 18개가 Merge되고 1개는 리뷰가 진행중인 상태이다. 컨트리뷰션 아카데이를 활동하면서 이슈를 찾고, 할당하는데 있어서 어려움이 적었고, 이슈를 해결하는데 재미를 느껴서 남들보다 더 많은 이슈들을 해결할 수 있었다.

    기능 추가 PR

    1. Table Columns Setting 구현

    https://github.com/lablup/backend.ai-webui/pull/2071

    인턴십 기간에서 목표로 했던 이슈 중 하나였다. 가을 인턴 기간에서 유일하게 기존에 있던 컴포넌트를 리팩토링이 아닌, 처음부터 구상하고 구현했다. 이 기능을 구현하기 전에는 간단한 기능이고 금방 끝낼 수 있을거라고 생각했지만 생각과 다르게 흘러갔다.

    우선 이전까지는 컴포넌트를 새롭게 만드는 것을 너무 쉽게 생각했구나를 많이 느꼈다. 이전에도 컴포넌트를 만들기 전에 디자인하고, 전달받을 props들을 생각하긴 했지만, 해당 이슈를 통해서 컴포넌트를 새롭게 만들 때, 확장성을 생각해서 조금 더 시간과 노력을 투자해야한다고 느꼈었다. 또한 다른 사이트들은 어떤 식으로 디자인되어 있는지, 적용된 기능들이 무엇이 있는지 좀 더 관심있게 봐야겠다를 느낄 수있었다.

    Table Columns Setting

    2. 모델 서빙 페이지 Table에 서비스 엔드포인트와 소유자 Column 추가

    https://github.com/lablup/backend.ai-webui/pull/2047

    기존에는 모델 서비스를 만들고 나면 endpoint를 상세 페이지에 들어가서 확인해야하는데, 자주 사용하는 기능이다보니 Table Column에 추가되었으면 하는 요청이 있었다. 또한 admin 계정에서는 같은 그룹에 속한 유저의 서비스도 다 보여지기 때문에, 소유자가 누구인지도 나타 낼 수 있는 Column이 보여지면 좋겠다는 의견이 있었다. 해당 기능을 구현하기 위한 GraphQL field는 이미 구현되어있었기 때문에 데이터를 받아오는 query의 field를 추가하여 엔드포인트와 서비스 소유자의 데이터를 받아왔고, Table의 Column을 추가하여 데이터를 나타냈다. 소유자 Column은 Admin 권한을 가진 계정에만 나타난다.

    구현 모습. Admin 계정에서 나오는 화면(왼쪽) 과 User계정에서 나오는 화면(오른쪽)

    3. CANCELLED 상태인 세션의 로그 버튼 비활성화

    https://github.com/lablup/backend.ai-webui/pull/2045

    CANCELLED 상태는 컨테이너가 생성된 적이 없거나 생성에 실패한 상태이다. 기존에는 CANCELLED 상태인 세션에도 로그 버튼이 활성화 되어있어, 사용자가 로그 버튼을 누르게된다면 에이전트가 컨테이너 정보를 찾을 수 없어 500에러가 나타났다. 그래서 해당 PR에서 CANCELLED 상태인 세션은 사용자들이 로그 버튼을 누를 수 없게 비활성화 하는 작업을 진행했다.

    TERMINATED 상태의 세션(1번 세션)과 CANCELLED 상태의 세션(2번 세션)

    4. Dark mode를 위한 테스트 및 custom hook 제작

    https://github.com/lablup/backend.ai-webui/pull/2120

    Dark mode를 적용하기 전 색깔을 하드코딩한 컴포넌트를 찾고, Dark mode 적용하기 위한 useThemeMode라는 이름의 custom hook을 구현했다. Custom hook을 만들 때 ahooks의 useLocalStorageState hook을 사용해서 구현하려고 했으나, 같은 Key값을 사용하는 state에 대해서 자동으로 state 관리가 되는 줄 알았으나 예상과 다르게 독립적으로 동작하는 것을 확인했다. 그래서 같은 Key값을 사용하는 state에 대해서 값이 바뀌면 자동으로 바뀌도록 구현하기 위해 useLocalStorageGlobalState 라는 이름의 custom hook을 추가했고, 해당 hook을 사용해서 Dark mode를 설정할 수 있는 useThemeMode라는 custom hook을 만들 수 있었다.

    Bug fix PR

    1. 초대 토큰 값 없이 회원가입 가능

    https://github.com/lablup/backend.ai-webui/pull/2046

    config.toml에서 allowSignupWithoutConfirmation이라는 옵션을 true로 변경하게되면, 초대 토큰없이 회원가입이 가능한데, 사용자가 회원가입 버튼을 누르게되면, 토큰 값이 undefined되어 있다고 에러를 발생했었다. 따라서 해당 PR에서는 allowSignupWithoutConfrimation의 옵션이 true면 token 변수를 사용하지 않도록 수정했다. 또한 이전에는 회원가입 버튼을 누르고 나서 core쪽에서 데이터를 처리하는 동안 다른 입력 값들을 수정할 수 있었으며, dialog가 닫힌 뒤, 다시 열면 이전 데이터가 남아있는 이슈가 있었는데, 해당 PR에서 데이터를 처리하는 동안 다른 입력 값들을 입력할 수 없게 설정했으며, dialog가 닫히면 이전에 입력한 값들이 clear되도록 설정했다.

    2. 사용자 관리 페이지에서 서브 탭에 맞는 화면 표시

    https://github.com/lablup/backend.ai-webui/pull/2055

    사용자 관리 페이지에는 활성화 상태인 사용자와, 비활성화 상태인 사용자들을 보여줄 수 있는 서브 탭이 존재한다. 하지만 해당 페이지에서 작업을 하다가 다른 페이지로 이동한 뒤 다시 사용자 관리 페이지로 돌아오면 서브 탭은 비활성으로 되어있지만, 실제 화면에는 활성화 상태인 사용자 리스트들이 나오는 문제가 있었다. 해당 문제는 사용자들에게 혼란을 줄 수 있는 부분이라 생각했고, 해당 PR에서 수정하게 되었다. 다른 페이지로 이동할 때, 현재 작업중인 서브 탭이 무엇인지 기억하여, 다시 해당 페이지로 돌아왔을 때 이전에 작업했던 서브 탭과 관련된 화면을 나타낼 수 있도록 수정했다.

    수정 전 사진(왼쪽)과 수정 후 사진(오른쪽)

    인턴 기간 연장

    이슈를 해결하다보니 8주라는 시간은 금방 지나갔고, 가을 인턴을 마무리해야하는 시간이 찾아왔다.

    군대를 전역하고 개발자로서 첫 사회생활이라 중요한 시기였는데 래블업에서 인턴을 하면서 나의 부족한 점이 무엇인지, 어떤 부분을 더 준비해야하는지, 다른 분들은 어떻게 개발을 하는지 등등 을 많이 느낄 수 있는 기간이었다. 2달이라는 시간이 매우 짧게 느껴졌고, 2달동안 매우 즐겁게 일을 했기 때문에, 좀 더 일해보고 싶다라는 생각이 들었다. 그래서 리더분께 인턴십 기간을 연장하고 싶다는 의견을 말씀드렸고, 8주를 더 연장하여 2월까지 인턴십을 진행하기로 했다. 그리고 가을 인턴에서는 나의 부족한 부분에 대해서는 많이 생각이 났지만, 나의 장점을 찾지 못했다. 그래서 아래 3가지 개인적인 목표를 세우며 시작하기로 다짐했다.

    1. 이번 기간에는 장점을 찾자
    2. 시간이 될 때마다 document를 읽자
    3. 아쉬움이 없을만큼 더 열심히 일해보자

    연장된 기간 이슈 해결 및 기능 구현

    연장된 기간에 하는 일이 이전과 크게 다르지 않았다. 온보딩 기간과 설치과정이 없어 이슈 해결에 좀 더 집중할 수 있는 기간이었다.

    기능 추가 PR

    1. ErrorLogList 리팩토링

    https://github.com/lablup/backend.ai-webui/pull/2131

    기존 Lit element로 구현되어 있던 ErrorLog List를 React로 리팩토링을 진행했다. 해당 기능들은 리팩토링을 하고나서 개인적으로도 잘 사용하고 있어 가장 만족스러운 이슈였다.

    리팩토링 전(왼쪽)과 리팩토링 후(오른쪽) 사진

    리팩토링을 진행하면서 기존에는 없었던 Search 기능과 Error filter 기능이 추가되었다.

    추가된 Search 기능(왼쪽)과 Filter 기능(오른쪽)

    2. Modal 드래그 기능

    https://github.com/lablup/backend.ai-webui/pull/2179

    React-draggable 라이브러리를 사용하여 Modal이 drag될 수 있도록 기능을 추가했다. Modal에 Draggable props를 추가하여 drag가 필요한 modal에서 적용할 수 있다.

    드래그 가능한 Modal

    Modal 제목 왼쪽에 있는 아이콘을 클릭하면서 마우스를 움직이면 Modal을 화면에 원하는 위치로 이동시킬 수 있다.

    현재는 사용자 관리페이지의 사용자 정보를 볼 수 있는 Modal과 사용자 설정을 변경할 수 있는 Modal에 적용되어 있어 확인할 수 있다.

    아직은 사용되는 곳이 많지는 않지만, 점점 컴포넌트들이 늘어나고, 기능들이 추가되면서 유용하게 사용될 수 있다고 생각한 PR이었다.

    Bug fix PR

    1. Vfolder 초대 권한 수정

    https://github.com/lablup/backend.ai-webui/pull/2143

    Group vfolder 사용자 권한이 수정되지 않는 문제가 있었다. 권한을 수정하려고 하면 select에서 item들이 제대로 표시 및 선택이 되지 않는 문제가 있었다. 기존에는 option 태그를 사용해서 item들을 표시해주고 있었는데, mwc-list-item으로 변경하여 item들을 표시해주었고, overflow 옵션을 수정하여 해당 이슈를 해결했다.

    PR 전 사진(왼쪽)과 PR 후 사진(오른쪽)

    2. ResourceGroupSelect이 Card밖으로 나가는 문제

    https://github.com/lablup/backend.ai-webui/pull/2166

    ResourceGroupSelect의 값이 너무 크면 Card밖으로 표시가 되는 문제가 있었다.

    문제가 되었던 증상들

    해당 문제를 해결하기 위해서 Select에 max-width css를 설정하여 Card의 width를 넘지 못하도록 하였다.

    또한 해당 PR에서 Select에서 Search기능을 추가했는데, 해당 기능을 추가하면서 ahooks의 useControllableValue라는 hook을 사용했다. useControllableValue는 부모나 자신이 props를 관리할 수 있게 도와주는 hook이다. 간단한 PR이었지만 useControllableValue를 처음 사용하다보니 생각보다 시간이 오래 걸린 PR이었다. 해당 이슈를 해결하면서 리더분과 다른 인턴분의 도움을 받아 해결할 수 있었다.

    3. 요약 페이지에서 키페어 생성&관리 버튼 눌렀을 때 키페어 리스트가 보여지지 않는 문제

    https://github.com/lablup/backend.ai-webui/pull/2194

    요약 페이지에는 새 키페어 생성, 키페어 관리 라는 버튼이 있는데, 해당 버튼을 누르면 단순히 사용자 관리 페이지로 이동하여 키페어 리스트가 아닌 유저 리스트가 보여지는 이슈가 있었다.

    요약 페이지에서 새 키페어 생성 버튼과 키페어 관리 버튼

    새 키페어 생성 버튼을 눌렀을 때(왼쪽) 과 키페어 관리를 눌렀을 때(오른쪽)

    해당 이슈는 크리티컬한 이슈는 아니었지만, 내가 처음으로 Backend.AI를 사용해보면서 키페어라는 기능을 잘 이해하지 못했을 때, 많이 혼동이 있었던 경험때문에 해결했었다.

    해당 이슈를 해결하고 나서는 의도한 대로 키페어 리스트들이 화면에 나타나는 것을 확인할 수 있다.

    이슈를 해결 한 뒤 새 키페어 생성 버튼을 눌렀을 때(왼쪽) 과 키페어 관리를 눌렀을 때(오른쪽)

    인턴십을 마치며

    전역을하고 친구의 추천을 시작하게된 컨트리뷰션 아카데미를 인연으로 래블업에서 긴 시간동안 기여를 할 수 있었다. 이전에 다른 회사의 인턴 경험, 프로젝트 경험이 없었기 때문에, 전역을 한 뒤 새롭게 시작하는 나한텐 있어서 매우 중요한 시기였는데, 래블업에서 활동할 수 있어서 나의 장단점, 부족한 기술, 오픈소스 회사의 문화 등을 경험할 수 있어서 너무 좋았다. 수평적인 구조, 자유로운 분위기, 쾌적한 근무 환경과 좋은 장비들 까지 매일 출근하고 싶게 만드는 회사가 몇이나 있을까? 4개월을 래블업에서 근무했지만, 매일 출근하고 싶고, 래블업이라면 내가 오랫동안 재미있고 원하는 일을 하면서 회사를 다닐 수 있겠다 라고 생각이 들었다. 4개월이라는 시간동안 래블업에서 서비스하고 있는 Backend.AI에도 정이 들어서, 매년 래블업에서 주최하는 컨퍼런스도 시간이 될 때마다 참가하여 발전된 모습과, 기술들을 보러 갈 예정이다.

    래블업 사무실

    이 포스트는 필자의 개인 블로그에 함께 게시되었습니다. https://gee05053.tistory.com/32

    11 March 2024

  • Backend.AI 와 Tool LLM 의 만남 : Tool 과 AI 의 협업 혁명 - 3부

    By Sergey Leksikov

    3부. 학습 및 서버 LLLM 없이 로컬에서 몇 줄의 코드만으로 자체 API 검색기 및 질문 답변 시스템을 만들기

    앞서 1부에서는 도구 LLM과 그 사용법에 대해 설명했습니다. 2부에서는 Backend.AI에서 Gorilla LLM을 실행하는 방법을 설명했습니다. 3부에서는 GPU를 사용할 수 없지만 API와 관련하여 도움과 지원을 받고자 하는 경우에 대해 이야기 해 보겠습니다.

    우리에게 Backend.AI가 있고, 질문과 답변 방식을 통해 보다 인터랙티브한 방식으로 Backend.AI REST API 및 Functional API에 대한 정보를 얻고 싶다고 가정해 보겠습니다. REST API의 예는 이 문서에서 설명할 수 있습니다(https://docs.backend.ai/en/latest/manager/rest-reference/index.html).

    그림 1. Backend.AI REST API 도큐먼트

    추가적으로 Backend.AI REST API 설명서를 openapi.json 형식으로 내보낼 수 있습니다:

    그림 2. Backend.AI openai.json

    Another source of BackendAI API is functional API defined in Backend.AI Client. We want to know how to interact with Backend.AI and which parts of code are responsible. The client code repository is responsible with managing and interacting with cloud and computing environment:

    Steps to make a Question Answering API system

    1. 로컬 PC 환경에서 https://github.com/lablup/backend.ai/tree/main/src/ai/backend/client 의 Backend.AI 클라이언트를 로컬로 설정하고 새 디렉토리 bai-dev/src/ai/backend/client/gpt_api_client 를 생성해 보겠습니다.

    그림 3. gpt_api_client 디렉토리 위치

    1. vector_data 아래에 REST API 문서: openapi.json 을 저장할 data1/ 그리고 API 질의응답 수행을 위해 선택된 B.AI 클라이언트 파일을 저장할 data2/ 두 개의 하위 디렉토리를 생성하겠습니다.

    그림 4. openapi.json 및 클라이언트 함수 코드 파일이 포함된 데이터 디렉터리 개요

    1. 파이썬 라이브러리인 LlamaIndex 를 설치합니다. Pip install llama-index 참고로 LlamaIndex 는 Meta 의 LLaMA 언어 모델과는 관련이 없습니다. LlamaIndex 는 검색을 위해 문서를 효율적으로 처리하고 저장하기 위한 데이터 구조 및 메서드와 관련이 있습니다.

    2. API와 코드 파일을 임베디드 벡터로 변환하고 LLamaIndex를 사용하여 벡터 데이터베이스에 저장해 보겠습니다. 로컬 PC에서 VSCode에 통합된 Jupyter Notebook 대화형 환경을 사용해 보겠습니다.

    그림 5. 주피터 노트북 대화형 환경. data/ 디렉터리에서 openapi.json을 로드. 이후 쿼리 엔진에서 벡터 인덱스를 통해 질문.

    1. 코드 함수를 사용하여 data2/ 디렉토리를 벡터화 합니다.

    그림 6. B.AI 클라이언트의 코드 파일을 이용하여 data2/ 디렉토리 로딩. 인덱스로 벡터화 후 질답 엔진 생성.

    객체를 저장하고 직렬화 하는데 일반적으로 사용되는 파이썬 Pickle 또는 Joblib 라이브러리를 사용하여 joblib.dump(index, "rest_api_index.joblib") 그리고 joblib.dump(index, "functional_index.joblib") 인덱스를 모두 저장하고 시스템에 로드가 가능합니다.

    1. 주피터 노트북 환경은 이미 대화형 방식으로 질문하고 답변을 받을 수 있는 기능을 제공하고 있습니다. 또한 저장된 벡터 인덱스를 FastAPI 서버에 불러와서 웹을 통해 질문에 답할 수 있습니다. 이전 2부에서는 Gorilla LLM으로 계산 세션을 설정했습니다. 이전 데모에서도 여전히 FastAPI 서버를 사용한 계산 세션이 있습니다.

    2. Backend.AI 클라우드 세션에서 rest_api_index.joblibfunctional_index.joblib 파일을 api_helper/ vFolder 로 전송 해 보겠습니다.

    3. server.py 파일에서 벡터 인덱스를 로딩하고 쿼리 엔진을 정의합니다.

    Figure 7. server.py 에서 인덱스 및 쿼리 엔진 파일 정의

    1. 각 쿼리 엔진에 대해 FastAPI 엔드포인트를 지정합니다.

    그림 8. REST 및 함수형 API 검색을 위한 코드 스니펫

    1. curl 명령을 사용하여 로컬 PC에서 서버 응답을 테스트합니다. 특정 엔드포인트에서 서버가 쿼리를 받으면 사용자로부터 응답을 받습니다.
    curl -X POST -H "Content-Type: application/json" -d '{"instruction":"Create a new session"}' http://127.0.0.1:8000/rest_api
    

    그림 9. curl 명령의 응답. 예제 1

    curl -X POST -H "Content-Type: application/json" -d '{"instruction":"Create a new session"}' http://127.0.0.1:8000/functional
    

    그림 10. curl 명령의 응답. 예제 2

    또한 사용자 입력을 받아 해당 엔드포인트로 전송하고 응답을 받는 웹 앱을 만들 수도 있습니다.

    그림 11. Backend.AI REST 및 함수형 API를 통한 질문 답변을 위한 웹 앱 프로토타입. 예제 1

    그림 12. Backend.AI REST 및 함수형 API를 통한 질문 답변을 위한 웹 앱 프로토타입. 예제 2

    맺음말

    3부에서는 오픈 소스 파이썬 라이브러리 LLamaIndex를 사용하여 로컬에서 질문-응답 시스템을 만드는 방법을 보여드리면서 문서와 Backend.AI 코드를 벡터 형식으로 변환하는 방법을 알아보았습니다. 질문과 답변은 Visual Studio Code 에서 플러그인을 통해 지원하는 주피터 노트북 환경에서 대화형 방식으로 수행할 수 있습니다. 또한 이러한 벡터 인덱스를 Gorilla LLM API 튜닝 모델이 서버로 있는 Backend.AI 클라우드 환경으로 옮기기로 결정했습니다. 그런 다음 네트워크를 통해 사용자를 지원하기 위해 API 질의응답 웹 앱을 구현했습니다.

    참고 자료:

    • LLama Index. https://docs.llamaindex.ai/en/stable/

    Backend.AI API 도우미 및 Gorilla LLM 데모 동영상입니다:

    30 January 2024

  • Backend.AI 와 Tool LLM 의 만남 : Tool 과 AI 의 협업 혁명 - 2부

    By Sergey Leksikov

    2부. Backend.AI 로 Gorilla LLM 모델 서빙하기

    이전 글에서는 Tool LLM의 기능과 사용법에 대해 설명했습니다. 이번 글에서는 Backend.AI 데스크탑 앱을 사용하면서 Backend.AI 클라우드에서 Gorilla LLM 모델을 실행하는 방법을 단계별로 데모 해 보겠습니다.

    그림 1. MacO에 설치된 Backend.AI 데스크탑 앱

    1. 시작 버튼을 누르면 세션 생성 메뉴가 나타납니다.

    그림 2. 새 세션 시작 화면

    1. NGC-Pytorch 23.07 이미지를 선택합니다.

    2. 모델 파일이 포함된 작업 디렉토리 vFolder를 첨부합니다. 디렉토리명 예 api_helper/

    그림 3. vFolder 첨부 화면

    1. 128 GB RAM 및 5 fGPU 크기의 리소스를 선택합니다.

    그림 4. 리소스 선택 화면

    1. Visual Studio Code 데스크탑 환경을 선택합니다.

    그림 5. IDE 환경 선택 화면

    1. /home/work/api_helper/ 디렉토리에 server.py 파일을 생성합니다.

    2. requirements.txt 파일을 생성합니다.

    그림 6. requirements.txt 파일 내용

    설치를 위해 다음 명령을 실행하세요: pip install -r requirements.txt

    그림 7. 설치 명령 실행

    1. server.py 파일을 추가하고 트랜스포머 라이브러리를 사용하여 토큰화 및 모델 로더를 정의합니다.

    그림 8. server.py 코드 스니펫

    1. 서버 IP 주소와 포트 번호를 명시합니다.

    그림 9. 서버 IP 주소 및 포트 번호 명시

    1. 다음 명령으로 모델을 실행합니다: python server.py

    그림 10. server.py 시작

    1. 생성된 서버에 접속합니다.

    VSCode는 기기에서 Backend.AI 클라우드 서버로 포트 터널링 세션을 자동으로 생성합니다. 로컬 호스트 주소에 액세스하여 서버 상태를 확인할 수 있으며, 요청은 Backend.AI 클라우드로 터널링됩니다. 또한 필요에 따라 다른 사용자 지정 엔드포인트를 정의할 수 있습니다.

    그림 11. 서버 실행 로그

    그림 12. VSCode 포트 포워딩 구성

    그림 13. 서버의 루트에 액세스하기

    여기까지는 Backend.AI Cloud에 계산 세션을 생성하고, api_helper/ vFolder 디렉터리에 요구사항.txt 파일과 server.py를 첨부했습니다. 그런 다음 HuggingFace 리포지토리에서 Gorilla LLM을 다운로드하고 추론/api .endpoint를 사용하여 계산 세션 메모리로 로드하는 FastAPI 서버를 시작합니다.

    1. API 추론 테스트 로컬 컴퓨터 명령줄에서 curl 요청을 생성하여 Gorilla LLM의 API 추론을 테스트할 수 있습니다:
    curl -X POST -H "Content-Type: application/json" -d '{"text":"Object detection on a photo. <<<api_domain>>>:"}' http://127.0.0.1:8000/inference
    

    그림 14. curl 명령 예제

    그림 15. 요청 수신 후 서버의 GPU 워크로드

    그림 16. 요청 수신 및 결과 인쇄에 대한 서버 로그

    1. UI 웹 앱을 정의합니다. 어떤 웹 기술이라도 사용하여 더 나은 방식으로 결과를 표시할 수 있는 UI 앱을 만들 수 있습니다. 예를 들어 html 및 JavaScript 파일을 사용하여 server.py의 루트 아래에 있는 정적 디렉터리에 배치한 다음 웹 앱의 엔드포인트를 정의할 수 있습니다.

    그림 17. FastAPI 서버에 html 웹 앱 추가 예시

    1. Gorilla LLM 웹 앱 프로토타입 - API 질문 답변 및 코드 생성을 위해 API가 튜닝된 대규모 언어 모델입니다.

    그림 18. Gorilla LLM 웹 앱 프로토타입. 예제 1

    그림 19. Gorilla LLM 웹 앱 프로토타입. 예제 2

    맺음말

    고릴라 LLM이 제공하는 몇 가지 어려움에도 불구하고, 자체 API로 튜닝된 LLM은 큰 잠재력과 가능성을 가지고 있습니다. 상용 대형 모델보다 더 정확한 파라미터와 함수 호출로 가장 최신의 결과를 제공할 수 있으며, API를 통한 질문 답변, 코드 자동 완성, API 코드 실행과 같은 작업에 유용하게 사용할 수 있기 때문입니다.

    한계점 및 어려움:

    Gorilla LLM 모델을 서버로 전송하는 동안 다음과 같은 문제를 고려해야 했습니다:

    • 모델이 예상과 다른 형식의 응답을 생성할 수 있음
    • 동일한 질문에 대해 모델이 다른 결과를 생성할 수 있음
    • LLM 응답 파싱 및 렌더링
    • 중복된 문장과 줄 제거하기

    29 January 2024

  • Backend.AI 와 Tool LLM 의 만남 : Tool 과 AI 의 협업 혁명 - 1부

    By Sergey Leksikov

    1부. LLM 와 Tool 의 협업 소개

    미래의 AI 기술 기능을 지금 바로 사용할 수 있다면 어떨까요? 아마도 직장에서 퇴근하는 길에 AI 어시스턴트에게 집에 도착하기 전에 집안의 에어컨을 켜달라고 요청할 수 있을 것입니다. 동시에 휴가를 계획하고 있는데 선택의 여지가 거의 없는 상황에서 AI 모델에게 호텔 예약을 대신 해달라고 요청할 수도 있습니다. 모델이 여행을 예약하면 클라우드 제공업체로부터 딥러닝 모델의 학습 진행 상황에 대한 알림을 받게 됩니다. AI 어시스턴트에게 성능 정확도를 위해 특정 값을 목표로 삼고 다른 매개변수 세트를 사용하여 다른 세션을 실행하도록 요청합니다. 이러한 미래적인 시나리오가 현재에 어떻게 실현될 수 있을까요?

    이러한 종류의 LLM과 실제 세계의 상호작용은 애플리케이션 프로그래밍 인터페이스(API)를 통해 가능합니다. API 데이터 세트에서 미세 조정된 특정 도구의 거대 언어 모델(LLM)은 특정 API로 사용자의 쿼리에 응답할 수 있으며, 해당 API는 프로그램이나 함수를 호출하여 실제 세계에 영향을 미칠 수 있습니다. 거대 언어 모델(LLM)은 문맥에 맞는 텍스트를 생성하는 뛰어난 기능과 문제 해결을 위한 추론 기능으로 인해 그 인기가 높아지고 있습니다. 텍스트 모델 활용 범위는 텍스트 생성, 편집뿐만 아니라 프로그래머의 코파일럿 역할까지 다양합니다. 텍스트 생성 기능 외에 LLM의 활용 범위를 확장할 수 있는 방법은 무엇일까요?

    Tool LLM을 통해 우리는 AI가 우리의 요청을 이해하는 것뿐만 아니라 다양한 온라인 도구를 사용하여 요청에 따라 행동할 수 있는 시대로 나아가고 있습니다. Tool LLM은 기능 및 REST API를 통해 도구를 통한 AI가 할 수 있는 일의 한계를 확장하고 있습니다.

    GPT-4는 현재 대부분의 AI 벤치마크에서 1위를 차지하고 있는 최신 LLM입니다. 이 시나리오에서 GPT-4 모델에 오디오 파일을 다른 언어의 텍스트로 변환하라는 요청을 받는 경우를 생각해 보겠습니다. 그러나 특정 API를 사용하라고 프롬프팅을 하게 되면 GPT-4는 존재하지 않는 API를 착각(hallucinate)하여 제안하거나 잘못된 인수를 제공할 수 있습니다. 결과적으로 기능 실행에 실패하여 사용자가 지정한 작업의 목표를 달성하지 못할 수 있습니다.

    환각(hallucinations)과 부정확성(inaccuracies) 문제 외에도 API 문서와 버전은 끊임없이 변화하고 있습니다. 범용 LLM을 재학습 하는 것은 비용이 많이 들고 지속적으로 변경되는 문서로 LLM 모델을 업데이트하는 것은 실용적이지 않습니다. Tool LLM은 프로그래밍 인터페이스를 통해 물리적 세계와 상호작용할 수 있도록 함으로써 일반적인 대형 모델의 환각 문제에 대한 해결책을 제시합니다. Tool LLM은 크기가 훨씬 작기 때문에 주기적으로 최신 데이터로 재학습할 수 있습니다. 또한 API 문서 검색 모듈을 모델 서빙 파이프라인에 추가하여 사용자의 입력 쿼리와 관련된 최신 API 문서로 모델을 보완할 수 있습니다.

    이러한 과제 극복을 위해 최근 연구자들은 각각 고유한 장점과 구체적인 사용 사례를 갖춘 Gorilla LLM, ToolLLaMA 와 같은 LLM 도구 사용 능력을 향상시키는 두 가지 주목할 만한 오픈 소스 방법을 제안했습니다. 또한 이러한 모델은 Backend.AI 클라우드에서 추론 서비스를 제공하기 위해 준비될 수 있습니다.

    Tool LLM이란 무엇인가요?

    Tool LLM은 사용자 쿼리가 포함된 데이터 세트와 API 코드 사용 및 API 설명 문서와 같은 관련 컨텍스트 정보가 포함된 API 요청에 대해 학습된 LLM입니다. 이러한 LLM의 응답은 코드로 실행될 수 있습니다. 코드 실행은 LLM이 다양한 온라인 서비스 및 도구와 상호 작용할 수 있음을 의미합니다. 클라우드 컴퓨팅 제공업체, Kubernetes 머신 러닝 및 딥 러닝 라이브러리, HuggingFace, TorchHub, TensorFlowHub 와 같은 리포지토리 등이 이에 해당합니다.

    이러한 도구 LLM의 가장 큰 장점은 사용자 쿼리에 대한 API 응답을 정확하게 생성하고 이를 실행하여 결과를 얻을 수 있다는 점입니다.

    API 유형 이해하기

    API(Application Programming Interface)는 현대 컴퓨팅 환경의 중요한 요소로, 서로 다른 소프트웨어 애플리케이션이나 하드웨어 시스템이 통신하고 상호 작용하는 방법에 대한 일련의 규칙과 프로토콜 역할을 합니다.

    함수형 API는 프로그래밍 환경 내에서 함수 콜을 통해 호출되도록 설계되었습니다. 예를 들어, HuggingFace 및 TensorFlow와 같은 머신 러닝 및 딥 러닝 라이브러리는 Functional API call을 통해 메모리에 로드하고 활용할 수 있는 다양한 모델을 제공합니다. 이러한 API는 소프트웨어 내에서 특정 기능과 연산을 실행하는 데 필수적입니다.

    API와 관련된 코드를 생성하는 LLM의 이러한 기능은 기본적인 텍스트 생성 및 처리 기능을 훨씬 뛰어넘어 그 활용도를 확장합니다. 도구 LLM은 클라우드 컴퓨팅 플랫폼에서 고급 머신 러닝 라이브러리에 이르기까지 다양한 온라인 서비스 및 도구와 원활하게 통합할 수 있습니다. 또한 사람의 쿼리에만 국한되지 않고 다른 프로그램이나 AI 에이전트와 상호 작용하는 시스템에도 통합할 수 있습니다. 이러한 다재다능함 덕분에 Tool LLM은 복잡한 시스템과 인프라에서 중요한 구성 요소로 자리매김하여 실제 애플리케이션에 대한 잠재력을 향상시킵니다.

    다음 섹션에서는 Tool LLM이 어떻게 학습되었고 어떻게 운영되는지 자세히 살펴보겠습니다. 그 다음은 Gorilla LLM과 ToolLLaMA라는 두 가지 구체적인 연구 사례를 다룰 것입니다.

    Tool LLM 학습 및 추론 워크플로

    Tool LLM 학습에는 API 데이터베이스 설정, 학습 데이터 세트 생성, 모델 학습 및 추론 등 여러 단계가 포함됩니다.

    API 데이터베이스에는 설명과 관련 코드 샘플이 포함되어 있습니다. Self-instruct 학습 데이터 세트를 생성하려면 API 데이터베이스 샘플을 {입력: 사용자 쿼리 - 출력: API} 쌍으로 사전 처리해야 합니다. ChatGPT는 사람이 물어볼 수 있는 다양한 시나리오와 복잡한 쿼리를 처리하여 이러한 데이터 세트를 자동으로 생성하는 데 도움을 줄 수 있습니다. 구체적인 사례부터 일반적이고 추상적인 사례까지. Self-instruct 데이터 세트가 생성된 후 모델은 사용자 입력 쿼리가 주어지면 API 측면에서 정확한 예측을 할 수 있도록 학습됩니다.

    Tool LLM 추론의 경우, LLM이 정확한 인수 매개변수로 응답할 뿐만 아니라 최신 API 설명서를 사용하는 것이 중요합니다. 따라서 최신 API 변경 사항으로 모델을 유지하는 데 도움이 되는 API 문서 검색기가 사용됩니다.

    그림 1. API Instruction 데이터 세트를 통한 Tool LLM 학습 및 추론 워크플로우 개요

    사례 연구: Gorilla LLM 및 ToolLLaMA

    Gorilla LLM

    Gorilla 는 API 호출 작성에서 GPT-4를 능가하는 미세 조정된 LLaMA 7B 기반 모델입니다. Gorilla의 주목할 만한 점은 다음과 같습니다:

    • API에 대한 정확한 입력 인수를 생성하는 데 있어 현재 LLM의 한계와 잘못된 API 사용에 대한 환각 경향을 해결합니다.
    • Gorilla 는 문서 API 검색기와 통합되어 문서의 실시간 변경 사항에 적응할 수 있으며, 이는 API가 얼마나 자주 업데이트 되는지를 고려할 때 상당한 이점입니다.
    • 개발자들은 이 모델의 능력을 평가하기 위해 APIBench라는 데이터 세트를 개발했으며, 여기에는 총 1,600개 이상의 API가 포함된 HuggingFace, TorchHub, TensorHub의 API가 포함되어 있습니다.
    • Gorilla는 환각 문제를 완화하고 LLM 출력의 신뢰성을 개선하는 것으로 추정됩니다. 또한, Gorilla는 AWS, GCP와 같은 클라우드 제공업체와 함께 작동하고 Kubernetes 클러스터를 관리할 수 있도록 업데이트 및 확장되었습니다.

    ToolLLaMA

    ToolLLaMA는 RapidAPI 리포지토리에 기반한 도구의 명령어 튜닝 데이터 세트인 ToolBench에서 미세 조정된 모델입니다. ToolLLaMA의 주요 키포인트는 다음과 같습니다:

    • ToolBench는 16,000개 이상의 실제 API를 다루며 다양한 명령어 세트와 솔루션 경로를 제공합니다.
    • 이 논문에서는 여러 도구 사용 및 다단계 추론과 같은 LLM의 추론 기능을 향상시키기 위해 새로운 심층 검색 기반 의사 결정 트리 알고리즘(Depth-First Search-Based Decision Tree : DFSDT)을 제안합니다.
    • ToolBench에서 미세 조정된 ToolLLAMA는 ChatGPT의 성능과 흡사하며 APIBench와 같이 배포되지 않은 데이터 세트에서 일반화 능력을 보여줍니다.

    두 논문 모두 방대한 API를 탐색하고 활용함으로써 실제 도구 사용에서 LLM의 기능의 한계를 뛰어넘었다는 데 의의가 있습니다. 이러한 발전은 실제 애플리케이션에 매우 중요합니다. 아래는 비교 요약 표입니다.

    그림 2. 두 API 튜닝 LLM 간의 비교 표

    Backend.AI와 ToolLLM 의 시너지 효과

    LLM의 학습 또는 모델 서비스에는 상당한 컴퓨터 리소스를 필요로 하는데, 특히 RAM 용량과 계산 속도가 높은 그래픽 처리 장치(GPU)에 대한 수요가 많기 때문입니다.

    Backend.AI는 다양한 모델을 구축, 학습 및 제공하기 위한 확장 가능한 기반을 제공합니다. Backend.AI에는 모델 추론을 위한 온디맨드 확장 기능과 외부 노드 추가를 통한 서비스 제공, 워크로드 최적화를 위한 부하 분산 기능이 포함되어 있습니다. Backend.AI 에는 고성능 LLM 추론에 사용할 수 있는 vLLM과 TensorRT 서버가 있습니다. 또한 사용자 친화적으로 잘 설계된 인터페이스와 파이프라인 메이커인 FastTrack 툴을 통해 다양한 복잡도의 컴퓨팅 환경 세션을 생성할 수 있습니다.

    맺음말

    다양한 인공지능 어시스턴트와 에이전트가 다양한 기기 및 서비스와 상호작용하는 미래 시나리오는 이러한 상호작용에 특화된 API와 Tool LLM 을 통해 실현될 수 있습니다. Gorilla LLM 과 ToolLLaMA 는 복잡한 업무에 활용할 수 있는 좋은 기회를 제공합니다. 이들이 어떻게 학습하고 서비스를 제공하는지에 대한 워크플로도 이해하기 쉽습니다. 머신 러닝 및 클라우드 관리 작업에는 Gorilla LLM을 사용하는 것을 추천할 수 있습니다. 보다 일반적인 API 사용, 다중 도구 및 다단계 사례에는 ToolLLaMA를 사용하는 것이 좋습니다.

    자체 API 문서나 코드에 대한 자체 모델을 학습시켜 코드를 이해하는 LLM 모델을 보유할 수 있다는 장점도 있습니다. 이러한 LLM은 관련 정보를 얻고자 하는 사용자를 지원하거나 상호 작용할 때 유용할 수 있습니다.

    자주 묻는 질문:

    • Q: 환각의 원인과 LLM의 한계는 무엇이며 Tool LLM에서 이를 어떻게 해결했나요?
    • A: 다른 대규모 언어 모델과 마찬가지로 GPT-4는 인터넷의 광범위하지만 오래되었거나 부정확할 가능성이 있는 데이터 세트를 학습하기 때문에 주로 환각과 부정확성과 같은 한계에 직면해 있습니다. 이러한 '환각'은 모델이 사실과 다르거나 현실에 근거하지 않은 정보를 자신 있게 생성하는 경우를 말하며, 이는 데이터의 크기나 물리적 세계와의 상호작용 부족이 아닌 순수 텍스트 기반 학습 데이터의 특성에서 비롯된 문제입니다. 이러한 문제를 해결하기 위해 전문화와 빈번한 업데이트에 중점을 두고 Tool LLM이 개발되고 있습니다. API 문서와 같은 특정 데이터 세트에 대한 미세조정을 거쳐 프로그래밍 인터페이스를 통해 실제 시스템과 직접 상호 작용하여 보다 정확하고 최신 정보를 얻을 수 있습니다. Tool LLM의 재학습 빈도는 애플리케이션과 관련 분야의 변화 속도에 따라 달라지며, 모델을 최신 트렌드와 정보로 최신 상태로 유지하기 위해 월별, 분기별 또는 연 2회 업데이트가 필요할 수 있습니다.
    • Q: 사용자 쿼리와 API 쌍 예시는 어떤 것들이 있을까요?
    • A: 다음 예제입니다.
    • User Query: "우주 탐사에 관한 이 기사를 요약하세요."
    • API Output: HuggingFace.summarize(text="기사 본문", model="facebook/bart-large-cnn")
    • User Query: "이 고객 리뷰의 감정은 어떤가요?"
    • API Output: HuggingFace.analyze_sentiment(text="고객 리뷰 본문", model="distilbert-base-uncased-finetuned-sst-2-english")
    • User Query: "이 사진에 있는 물체를 식별하세요."
    • API Output: HuggingFace.image_recognition(image_file="path/to/photo.jpg", model="google/vit-base-patch16-224")
    • User Query: "이 음성 녹음을 텍스트로 변환하세요."
    • API Output: HuggingFace.speech_to_text(audio_file="path/to/recording.wav", model="facebook/wav2vec2-base-960h")
    • Q: 모델을 훈련하고 추론하는 과정에서 API 문서를 활용하는 방식에 있어 GorillaLLM과 ToolLLaMA 논문은 어떻게 다른가요?
    • A: GorillaLLM은 학습 중에 관련 API 문서를 추가하고 두 가지 추론 모드를 제공하는 반면, ToolLLaMA는 API 도메인에서 임베딩을 미세 조정하기 위해 Sentence-BERT를 사용합니다. 문서 검색을 위해 GorillaLLM은 LLamaIndex의 BM25와 GPT-Retriever를 사용하는 반면, ToolLLaMA는 비슷한 목적으로 Sentence-BERT를 사용합니다.
    • Q: 소규모 API 모델은 얼마나 자주 재교육해야 하며, API 리트리버는 API 문서 변경 사항을 처리하는 데 어떤 역할을 하나요?
    • A: 소규모 API 모델을 매년 교육하는 것은 합리적이지만, API 변경 사항에 대해 매달 재교육하는 것은 현실적이지 않습니다. API 리트리버는 최신 문서를 사용하여 잦은 재교육의 필요성을 줄일 수 있습니다. 미세 조정된 API 모델과 RAG 방법을 평가하고 벤치마킹하는 것은 효율성을 위해 필수적입니다.
    • Q: ToolLLM과 RAG 시스템의 차이점은 무엇이며, LLM의 맥락에서 어떻게 작동하나요?
    • A: ToolLLM은 지식을 통합하는 데 중점을 두고 API 문서에 따라 미세 조정된 모델입니다. 반면에 RAG 시스템은 데이터 청킹, 저장, 검색, 재순위 지정 및 합성을 위한 알고리즘입니다. 이들은 독립적으로 또는 함께 작동하여 특히 컨텍스트 제한 및 지식 업데이트를 처리할 때 LLM 효율성을 향상시킬 수 있습니다.

    참고 자료:

    • Gorilla: Large Language Model Connected with Massive APIs. https://gorilla.cs.berkeley.edu/
    • ToolLLM: Facilitating Large Language Models To Master 16000+ Real-World APIs. https://github.com/OpenBMB/ToolBench

    28 January 2024

  • Backend.AI를 위한 Raft 합의 알고리즘: 리더 선출

    By 강정석

    현대 어플리케이션을 이야기할 때 고가용성(High Availability, HA)은 빼놓을 수 없는 개념이 되었습니다. 고가용성은 IT 시스템이 다운타임을 제거하거나 최소화하여 거의 100% 상시 액세스 가능하고 신뢰성을 유지하는 능력을 의미합니다^1. 래블업이 개발하고 서비스하는 Backend.AI도 고가용성을 유지하기 위하여 다양한 방법을 적용하고 있습니다.

    Backend.AI의 구조도

    배경

    Backend.AI는 매니저와 에이전트, 스토리지 프록시와 웹서버 등 다양한 컴포넌트로 구성됩니다. 각 컴포넌트들은 각각 분산 환경에서 다중 프로세스로 실행되어 안정성을 높이고 있습니다. 특히 매니저 경우 Backend.AI의 세션 실행 스케줄링 및 여러 핵심 기능을 담당하고 있기 때문에 특히 더 높은 신뢰성을 보장해야 합니다. 현재 매니저에는 부하 분산을 통해 고가용성을 보장하는 Active-Active HA 구조가 적용되고 있습니다.

    Backend.AI 매니저의 여러 기능 중 하나는 바로 이벤트 처리입니다. Backend.AI는 에이전트와 세션의 생명 주기(Lifecycle)를 추적하고 최적의 스케줄링을 제공하기 위하여 AgentStartedEvent, DoScheduleEvent 등 다양한 이벤트를 발생시킵니다. 예를 들어 한 Backend.AI Agent 프로세스가 실행될 때 AgentStartedEvent를 생성하게 되고, 이 이벤트를 수신한 Backend.AI Manager 프로세스는 특정 동작(schedule())을 수행하게 됩니다. 또한 Backend.AI Manager는 내부적으로 DoScheduleEvent를 발생시키며 주기적인 스케줄링을 보장합니다. 이때 문제가 발생합니다. 고가용성을 위하여 여러 개의 Backend.AI Manager 프로세스를 실행할 경우, 각 프로세스가 자체적인 타이머를 갖고 이벤트를 발생시킨다면 불필요한 부하가 가해지는 것과 더불어 전체 시스템의 상태가 보장되지 못할 수 있게 됩니다. Backend.AI 매니저는 동일 시스템 내에서 오직 하나의 매니저 프로세스만 이벤트를 생성하는 것을 보장하기 위하여 GlobalTimer를 구현하였습니다. GlobalTimer는 분산 락(Distributed Lock)을 통해 프로세스 간 상호배제성을 확보하고, 오직 하나의 프로세스에서만 이벤트가 발생하도록 합니다.

    @preserve_termination_log
    async def generate_tick(self) -> None:
        try:
            await asyncio.sleep(self.initial_delay)
            if self._stopped:
                return
            while True:
                try:
                    async with self._dist_lock:
                        if self._stopped:
                            return
                        await self._event_producer.produce_event(self._event_factory())
                        if self._stopped:
                            return
                        await asyncio.sleep(self.interval)
                except asyncio.TimeoutError:  # timeout raised from etcd lock
                    if self._stopped:
                        return
                    log.warn("timeout raised while trying to acquire lock. retrying...")
        except asyncio.CancelledError:
            pass
    

    현재 Backend.AI는 분산 락에 대한 인터페이스인 AbstractDistributedLock을 제공하고 있으며, 실제 구현체로는 FileLock, etcd concurrency API 기반의 EtcdLock, Redis Lock 기반의 RedisLock을 개발하여 사용하고 있습니다.

    etcd는 분산 시스템을 계속 실행하는 데 필요한 중요한 정보를 보관하고 관리하는 데 사용되는 분산 오픈소스 키-값 저장소이며^2, 대표적으로 Kubernetes 등에서 사용되고 있습니다.

    class AbstractDistributedLock(metaclass=abc.ABCMeta):
        def __init__(self, *, lifetime: Optional[float] = None) -> None:
            assert lifetime is None or lifetime >= 0.0
            self._lifetime = lifetime
    
        @abc.abstractmethod
        async def __aenter__(self) -> Any:
            raise NotImplementedError
    
        @abc.abstractmethod
        async def __aexit__(self, *exc_info) -> Optional[bool]:
            raise NotImplementedError
    

    요구사항

    GlobalTimer는 분산 환경에서 프로세스 단위로 이벤트 생성을 제어하는 역할을 잘 수행하고 있습니다. 하지만 요구사항은 늘 변화하고 소프트웨어는 그에 발맞춰 변화해야 합니다. 이번에 추가된 요구사항은 요청 횟수 제한(rate limit)을 구현하는 것이었습니다. 현재와 같은 부하 분산 방식으로는 매 요청이 동일한 매니저에서 처리된다고 보장할 수 없는데, 각 매니저의 상태가 공유되지 않기 때문에 아래와 같은 문제가 발생할 수 있습니다.

    1. 두 매니저의 카운터를 각각 0으로 설정하고 요청 횟수 제한을 1로 설정합니다.
    2. 첫 요청을 1번 매니저가 받습니다.
    3. 1번 매니저의 카운터를 1만큼 증가시킵니다. (C1: 0 -> 1)
    4. 카운터가 최대 허용 횟수에 도달하여 다음 요청은 거절하게 됩니다.
    5. 부하 분산에 의해 두 번째 요청을 2번 매니저가 받습니다.
    6. 2번 매니저의 카운터는 아직 0이기 때문에 최대 허용 횟수에 도달하지 않았습니다. (C2: 0)
    7. 2번 매니저가 요청을 처리합니다.
    8. 요청 횟수 제한이 제대로 동작하지 않았습니다!
    

    따라서 이런 한계점을 개선할 방법을 논의하기 위하여 아래와 같은 이슈가 제안되었습니다.

    분산 타이머 개선을 제안하는 이슈 (lablup/backend.ai#415)

    리더로 표현되는 단일 매니저 프로세스에 전역 상태 관리를 위임하기 위하여 합의 알고리즘(Consensus algorithms)을 조사하게 되었고, Kubernetes의 저장소로 사용되는(https://kubernetes.io/docs/concepts/overview/components/#etcd) etcd 등의 프로젝트에서 사용되며 충분한 검증을 거쳤다고 판단되는 Raft Consensus Algorithm(이하 Raft)을 이용하기로 결정했습니다.

    Raft 합의 알고리즘

    Raft 알고리즘은 2014년 USENIX에 제출된 "In Search of an Understandable Consensus Algorithm"^3에서 제안된 방법입니다. 당대 최고의 알고리즘이던 Paxos^4는 복잡한 합의 과정으로 인하여 실제로 이해하고 구현하는 데 어려움이 있었고, 제목에도 드러나듯 이러한 문제점을 개선하기 위하여 만들어졌습니다.

    But our most important goal — and most difficult challenge — was understandability.

    • In Search of an Understandable Consensus Algorithm

    Raft 클러스터는 일반적으로 5개의 노드로 구성되는데, 최대 2대의 노드에 문제가 발생해도 quorum을 만족하여 시스템을 유지할 수 있기 때문입니다. 클러스터를 구성하는 각 노드는 아래의 세 가지 상태(리더, 팔로워, 후보자) 중 하나를 가집니다. 일반적으로 각 클러스터에는 최대 한 개의 리더가 존재할 수 있고, 나머지 노드는 팔로워가 됩니다.

    용어 설명 #1

    • quorum: 의결(議決)에 필요한 최소한도의 인원수를 의미합니다. (N/2+1)
    Raft 노드의 상태 전이 다이어그램 (출처: In Search of an Understandable Consensus Algorithm)

    Raft 알고리즘은 선출된 리더에게 모든 권한을 위임하며, 로그의 흐름을 일방향으로 만듦으로써 전체적인 흐름을 이해하기 쉽도록 만듭니다. Raft 알고리즘은 아래와 같은 특징을 가집니다.

    용어 설명 #2

    • term: 현재 리더 혹은 후보자의 세대를 의미합니다. 리더 선거가 시작될 때마다 1씩 증가합니다.
    • index: 로그에서 특정 값의 위치를 의미합니다.
    • commit: 로그에 있는 특정 값을 상태 머신에 적용하였음을 나타냅니다.
    • commitIndex: 커밋에 성공한 가장 높은 index
    • Election Safety: 각 term에는 최대 하나의 리더가 존재합니다.
    • Leader Append-Only: 리더는 로그를 덮어쓰거나 삭제하지 않고 새로 추가만 가능합니다.
    • Log Matching: 두 로그에 동일한 index와 term을 가진 값이 있다면, 해당 index까지의 모든 값은 동일합니다.
    • Leader Completeness: 특정 term에 어떤 값이 로그에 commit되었다면, 이후 세대의 모든 리더는 이 값을 가지는 것을 보장합니다.
    • State Machine Safety: 한 서버가 특정 index의 로그 값을 상태 머신에 적용하였다면, 다른 서버는 동일한 index에 있는 다른 값을 적용할 수 없습니다.

    위의 특징을 이용하여 Raft는 전체 합의 과정을 서로 독립적인 세 부분으로 나눕니다.

    • Leader election: 기존 리더가 동작하지 않으면 새 리더가 선출되어야 합니다.
    • Log replication: 리더는 클라이언트로부터 받은 요청 로그를 다른 노드에 복제합니다. 이때 다른 노드들은 리더의 로그를 무조건적으로 수용합니다.
    • Safety: 한 서버가 특정 index의 로그 값을 상태 머신에 적용하면 다른 서버는 동일한 index의 다른 값을 적용할 수 없습니다.

    이번 글에서는 Raft 노드가 가지는 각 상태에 대하여 알아보고 리더 선출 과정을 코드로 구현해 보도록 하겠습니다.

    팔로워(Follower)

    팔로워는 자체적으로 요청을 보내지 않고 리더 혹은 후보자의 요청을 받아 대응하는 역할만 수행합니다. 논문에서 제안하는 팔로워의 행동 명세(Behavior Spec)와 이를 기반으로 작성된 코드는 아래와 같습니다.

    • 리더와 후보자의 RPC 요청을 처리합니다.
    async def on_append_entries(
        self,
        *,
        term: int,
        leader_id: RaftId,
        prev_log_index: int,
        prev_log_term: int,
        entries: Iterable[raft_pb2.Log],
        leader_commit: int,
    ) -> Tuple[int, bool]:
        await self._reset_timeout()
        if term < (current_term := self.current_term):
            return (current_term, False)
        await self._synchronize_term(term)
        return (self.current_term, True)
    
    async def on_request_vote(
        self,
        *,
        term: int,
        candidate_id: RaftId,
        last_log_index: int,
        last_log_term: int,
    ) -> Tuple[int, bool]:
        await self._reset_timeout()
        async with self._vote_request_lock:
            if term < (current_term := self.current_term):
                return (current_term, False)
            await self._synchronize_term(term)
    
            async with self._vote_lock:
                if self.voted_for in [None, candidate_id]:
                    self._voted_for = candidate_id
                    return (self.current_term, True)
            return (self.current_term, False)
    
    async def _synchronize_term(self, term: int) -> None:
        if term > self.current_term:
            self._current_term.set(term)
            await self._change_state(RaftState.FOLLOWER)
            async with self._vote_lock:
                self._voted_for = None
    
    • 일정 시간 동안 리더 혹은 후보자로부터 아무런 요청을 받지 못하면 후보자 상태가 됩니다.
    async def _wait_for_election_timeout(self, interval: float = 1.0 / 30) -> None:
        while self._elapsed_time < self._election_timeout:
            await asyncio.sleep(interval)
            self._elapsed_time += interval
        await self._change_state(RaftState.CANDIDATE)
    

    리더는 주기적으로 팔로워들에게 하트비트(heartbeat) 메시지를 보냄으로써 자신의 존재를 알려야 합니다. 팔로워는 일정 시간(election_timeout) 동안 아무런 메시지를 받지 못하면 클러스터에 리더가 없는 것으로 판단하고, 자신이 새로운 리더가 되기 위하여 후보자가 되어 선거를 시작합니다.

    후보자(Candidate)

    후보자의 행동 명세와 구현 코드는 다음과 같습니다.

    • 새로운 리더로부터 AppendEntries RPC 요청을 받으면 팔로워가 됩니다. (팔로워의 on_append_etries() 참고)
    • 아래의 절차를 통해 선거를 시작합니다.
      • term을 1만큼 증가시킵니다. (term += 1)
      • 자신에게 투표합니다.
      • 선거 제한시간을 초기화합니다.
      • 다른 노드들에 RequestVote RPC 요청을 보냅니다.
    async def _start_election(self) -> None:
        self._current_term.increase()
        async with self._vote_lock:
            self._voted_for = self.id
    
        current_term = self.current_term
    
        terms, grants = zip(
            *await asyncio.gather(
                *[
                    asyncio.create_task(
                        self._client.request_vote(
                            to=server,
                            term=current_term,
                            candidate_id=self.id,
                            last_log_index=0,
                            last_log_term=0,
                        ),
                    )
                    for server in self._configuration
                ]
            )
        )
    
    • 과반수 이상의 노드로부터 득표하면 리더가 됩니다.
        for term in terms:
            if term > current_term:
                await self._synchronize_term(term)
                break
        else:
            if sum(grants) + 1 >= self.quorum:
                await self._change_state(RaftState.LEADER)
    
    • 선거 제한시간이 초과되면 새 선거를 시작합니다.
    case RaftState.CANDIDATE:
        while self.__state is RaftState.CANDIDATE:
            await self._start_election()
            await self._reset_election_timeout()
            await self._initialize_volatile_state()
            if self.has_leadership():
                await self._initialize_leader_volatile_state()
                break
            await asyncio.sleep(self.__election_timeout)
    

    리더(Leader)

    • 선출 직후 최초의 하트비트(텅 빈 AppendEntries 요청) 메시지를 보냅니다. 이후 주기적으로 하트비트 메시지를 보냅니다.
    async def _publish_heartbeat(self) -> None:
        if not self.has_leadership():
            return
        terms, successes = zip(
            *await asyncio.gather(
                *[
                    asyncio.create_task(
                        self._client.append_entries(
                            to=server,
                            term=self.current_term,
                            leader_id=self.id,
                            prev_log_index=0,
                            prev_log_term=0,
                            entries=(),
                            leader_commit=self._commit_index,
                        ),
                    )
                    for server in self._configuration
                ]
            )
        )
        for term in terms:
            if term > self.current_term:
                await self._synchronize_term(term)
                break
    
    • 클라이언트로부터 요청을 받으면 로그에 값을 추가합니다. 해당 값이 상태 머신에 적용된 후 요청에 대한 응답을 보냅니다.
    • 팔로워가 리더가 추적하고 있는 값(nextIndex)보다 더 큰 index의 로그 값을 가지고 있을 경우, nextIndex부터 시작하는 로그를 팔로워에게 복제합니다.
      • 성공할 경우 리더의 nextIndex와 matchIndex를 갱신합니다.
      • 불일치(inconsistency)로 인해 실패할 경우 리더의 nextIndex를 감소시키고 다시 시도합니다.
    • 아래와 같은 값(N)이 존재할 경우 commitIndex를 해당 값으로 갱신합니다.
      • 과반수 이상의 matchIndex가 N 이상임 (matchIndex >= N)
      • N번째 로그의 term이 현재 term과 동일함

    리더는 팔로워들에 대하여 각각 nextIndex와 matchIndex를 관리합니다.

    • nextIndex: 각 팔로워에게 보내야 할 다음 인덱스
    • matchIndex: 각 팔로워에게 성공적으로 복제한 가장 높은 인덱스

    마무리

    이번 글에서는 Raft 알고리즘에 대하여 간단히 알아본 후 리더 선출을 수행하는 코드를 작성했습니다. 나머지 두 가지 기능(로그 복제, 멤버십 변경)은 실제로 구현하는 과정에서 타이밍 이슈 등 여러 다양한 문제를 마주하게 됩니다. 만약 Raft 알고리즘에 대하여 더 알고 싶으시다면 저자(Diego Ongaro)의 박사 학위 논문(CONSENSUS: BRIDGING THEORY AND PRACTICE)^6를 읽어보는 것을 추천합니다.

    마지막으로 ChatGPT는 Raft 알고리즘에 대하여 어떻게 설명해 주는지 확인하며 글을 마치겠습니다.

    ChatGPT가 설명하는 Raft 알고리즘 (출처: OpenAI ChatGPT 3.5)

    본 글은 lablup/aioraft-ng의 코드를 참고하여 작성되었습니다. 현재 래블업에서 개발하고 있는 차세대 Raft 프로젝트인 lablup/raftify에도 많은 관심 부탁드립니다.

    29 November 2023

  • Backend.AI Model Service Hands-on: GPT-NeoX 실행하기

    By 조규진

    Backend.AI 23.09 버전이 정식으로 공개되었습니다. 23.09 버전의 핵심 기능인 Model Service에 대해서는 이전의 Sneak Peek: Backend.AI Model Service 미리 보기 글에서 다뤘던 바가 있습니다. 그 이후로 GUI 지원, 인증 토큰 이력 관리 등의 다양한 새로운 기능이 추가되었는데요, 이러한 새 기능을 포함해서 Backend.AI Model Service를 쉽고 간편하게 이해할 수있도록 튜토리얼 형식으로 따라가 보는 시간을 가져보도록 하겠습니다.
    이번 튜토리얼 게시글에서는 Backend.AI Model Service를 이용해서 GPT-NeoX 모델을 Triton Inference Server 위에서 구동하는 법에 대해 안내합니다. Triton Inference Server는 NVIDIA에서 내놓은 오픈 소스 모델 인퍼런스 프레임워크이며, 자사의 TritonRT, FasterTransformer 및 TritonRT-LLM 및 PyTorch, TensorFlow, vLLM 등의 다양한 모델을 HTTP 및 gRPC 1 로 간편하게 제공할 수 있습니다.

    Model VFolder 생성

    1. 데이터 & 폴더 탭으로 이동합니다. "새 폴더" 버튼을 클릭하여 VFolder 생성 다이얼로그를 엽니다.
    2. 새 모델 폴더를 생성합니다. 폴더 이름은 어떻게 적어도 관계 없지만 하단의 "사용 방식" 을 "Model" 로 설정해야 합니다. 모든 값을 지정하였으면 하단의 "생성" 버튼을 클릭합니다. 이제 모델 VFolder가 생성되었습니다.

    FasterTransformer 형식 모델 변환

    1. "세션" 탭으로 이동합니다. "시작" 버튼을 클릭하여 세션 생성 다이얼로그를 엽니다.
    2. "실행 환경" 을 ngc-pytorch 로, 버전23.07 을 선택합니다. 선택이 완료되었으면 오른쪽 아래의 화살표 아이콘을 클릭합니다.
    3. 세션에 탑재할 VFolder를 선택하는 창입니다. 모델을 적재하기 위해 "마운트할 모델 스토리지 폴더" 섹션 아래에서 방금 생성한 VFolder를 선택합니다. 선택이 완료되었으면 오른쪽 아래의 화살표 아이콘을 클릭합니다.
    4. 모델 세션에서 사용할 자원량을 지정하는 창입니다. CPU 코어를 16개 이상, RAM은 128GB 이상 할당하여야 원활한 모델 변환이 가능합니다. 선택이 완료되었으면 오른쪽 아래의 화살표 아이콘을 클릭합니다.
    5. 모든 설정이 올바르게 적용되었는지 확인한 후 아래의 "시작" 버튼을 클릭하여 세션을 시작합니다.
    6. 세션이 생성되면 다음과 같이 앱을 선택하는 팝업이 나타납니다. "Console" 앱을 클릭하여 터미널 환경으로 접근합니다.
    7. 다음 쉘 스크립트를 실행하여 GPT-NeoX 20B 모델을 다운로드하고 FasterTransformer 형식에 맞게 변환합니다. 스크립트에서 <VFolder 이름> 이라고 언급된 부분을 생성한 모델 VFolder 이름으로 치환해서 실행해야 함에 주의하세요.
    cd /home/work/<VFolder 이름>
    pip install -U transformers bitsandbytes
    git clone https://github.com/NVIDIA/FasterTransformer
    git clone https://huggingface.co/ElutherAI/gpt-neox-20b
    cd neo-gptx-20b
    git lfs install
    git lfs pull
    

    GPT-NeoX 20B 모델은 실행에 40GB 이상의 VRAM을 요구합니다. 사용할 물리 GPU의 VRAM이 이보다 작아 모델을 여러 GPU에 나눠서 실행해야 할 경우, -i_g 매개 변수의 숫자를 사용할 GPU 갯수에 맞춰서 조정하세요.

    cd /home/work/<VFolder 이름>
    mkdir -p triton-deploy/gpt-neox-20b-ft
    python ~/<VFolder 이름>/FasterTransformer/examples/pytorch/gptneox/utils/huggingface_gptneox_convert.py \
      -i /home/work/<VFolder 이름>/gpt-neox-20b \
      -o /home/work/<VFolder 이름>/triton-deploy/gpt-neox-20b-ft \
      -i_g 1 \
      -m_n GPT-NeoX-20B
    

    1. 7번까지의 과정을 모두 완료했다면 VFolder 아래에 다음과 같은 폴더들이 존재할 것입니다.
    work@main1[PRRLCIqu-session]:~/GPT-NeoX-Triton-FT$ ls -al
    total 62
    drwxr-xr-x  5 work work 11776 Oct 12 12:14 .
    drwxr-xr-x  9 work work  4096 Oct 12 12:29 ..
    drwxr-xr-x 14 work work 12800 Oct 12 11:24 FasterTransformer
    drwxr-xr-x  3 work work 16896 Oct 12 10:18 gpt-neox-20b
    drwxr-xr-x  3 work work 11776 Oct 12 11:56 triton-deploy
    

    이제 Triton Inference Server의 설정 파일을 추가할 차례입니다. triton-deploy/gpt-neox-20b-ft/config.pbtxt 파일을 생성하고 다음 내용을 추가합니다.

    7번 과정에서 -i_g 매개 변수의 값을 1이 아닌 다른 값으로 설정했을 경우, 아래 설정의 tensor_para_size 값을 -i_g 값과 일치하도록 수정해야 합니다.

    name: "gpt-neox-20b-ft"
    backend: "fastertransformer"
    default_model_filename: "gpt-neox-20b-ft"
    max_batch_size: 1024
    
    model_transaction_policy {
      decoupled: False
    }
    
    input [
      {
        name: "input_ids"
        data_type: TYPE_UINT32
        dims: [ -1 ]
      },
      {
        name: "start_id"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "end_id"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "input_lengths"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
      },
      {
        name: "request_output_len"
        data_type: TYPE_UINT32
        dims: [ -1 ]
      },
      {
        name: "runtime_top_k"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "runtime_top_p"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "beam_search_diversity_rate"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "temperature"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "len_penalty"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "repetition_penalty"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "random_seed"
        data_type: TYPE_UINT64
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "is_return_log_probs"
        data_type: TYPE_BOOL
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "beam_width"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "bad_words_list"
        data_type: TYPE_INT32
        dims: [ 2, -1 ]
        optional: true
      },
      {
        name: "stop_words_list"
        data_type: TYPE_INT32
        dims: [ 2, -1 ]
        optional: true
      },
      {
        name: "prompt_learning_task_name_ids"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "top_p_decay"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "top_p_min"
        data_type: TYPE_FP32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      },
      {
        name: "top_p_reset_ids"
        data_type: TYPE_UINT32
        dims: [ 1 ]
        reshape: { shape: [ ] }
        optional: true
      }
    ]
    output [
      {
        name: "output_ids"
        data_type: TYPE_UINT32
        dims: [ -1, -1 ]
      },
      {
        name: "sequence_length"
        data_type: TYPE_UINT32
        dims: [ -1 ]
      },
      {
        name: "cum_log_probs"
        data_type: TYPE_FP32
        dims: [ -1 ]
      },
      {
        name: "output_log_probs"
        data_type: TYPE_FP32
        dims: [ -1, -1 ]
      }
    ]
    instance_group [
      {
        count: 1
        kind: KIND_CPU
      }
    ]
    parameters {
      key: "tensor_para_size"
      value: {
        string_value: "1"
      }
    }
    parameters {
      key: "pipeline_para_size"
      value: {
        string_value: "1"
      }
    }
    parameters {
      key: "data_type"
      value: {
        string_value: "fp16"
      }
    }
    parameters {
      key: "model_type"
      value: {
        string_value: "GPT-NeoX"
      }
    }
    parameters {
      key: "model_checkpoint_path"
      value: {
        string_value: "/models/triton-deploy/gpt-neox-20b-ft/1-gpu"
      }
    }
    parameters {
      key: "enable_custom_all_reduce"
      value: {
        string_value: "0"
      }
    }
    
    1. 마지막으로 Backend.AI Model Service 정의 파일을 VFolder 루트 아래에, model-definition.yaml (model-definition.yml 도 허용) 추가해야 합니다. Triton Inference Server를 실행하기 위한 모델 정의 파일을 자세히 들여다 보겠습니다.
    models:
    - name: "GPT-NeoX"
      model_path: "/models/triton-deploy"
    ...
    

    모델 이름과 모델의 경로를 지정하는 부분입니다.

    여기서 설정한 이름과 경로는 모델 서버 프로세스에서 각각 BACKEND_MODEL_NAME, BACKEND_MODEL_PATH 환경 변수로 접근할 수 있습니다.

    ...
      service:
        start_command:
          - tritonserver
          - --model-repository=/models/triton-deploy
          - --disable-auto-complete-config
          - --log-verbose
          - "1"
    ...
    

    모델 서버 프로세스를 시작하기 위한 명령줄 구문을 정의하는 부분입니다.

    ...
        port: 8000
    ...
    

    모델 서버 프로세스가 노출하는 API 통신용 포트를 기입하는 부분입니다. 지정하지 않은 경우, Triton Inference Server는 기본적으로 HTTP API 통신을 위해 8000 번 포트를 노출합니다. 그러므로 모델 정의 파일에도 해당 포트를 그대로 적어줍니다.

    ...
        health_check:
          path: /v2/health/ready
          max_retries: 3
          max_wait_time: 5
          expected_status_code: 200
    

    Health Check 기능을 활성화 및 설정하는 부분입니다. Health Check 기능이 활성화 된 경우, Backend.AI에서는 해당 경로에 지속적으로 HTTP GET 요청을 보내서 expected_status_code (생략 가능, 기본값 200) 에 해당하는 HTTP 응답 코드를 반환하는지를 검증합니다. 만약 모델 서버가 응답하지 않거나, 혹은 정의되지 않은 응답 코드를 반환하는 경우, Backend.AI는 해당 세션을 불량한 (Unhealthy) 세션으로 판단하고 서비스에서 제외합니다. 세션이 서비스에서 제외되더라도 해당 세션은 자동으로 종료되지 않으며 Model Service 관리자가 컨테이너 로그 등을 확인하여 적절한 조치를 직접 취해야 합니다.
    Health Check 기능은 해당 구문을 완전히 생략하는 것으로 비활성화 시킬 수 있습니다. 이렇게 할 경우 Backend.AI는 모델 서버의 상태를 검사하지 않고 항상 Healthy 상태인 것으로 간주합니다.
    max_wait_time 은 API 응답 Timeout을 정의하는 부분입니다. 초 단위의 숫자를 기입해야 합니다.
    max_retries 는 해당 모델 서버를 Unhealthy 상태로 판단하기 전까지 요청을 재시도하는 회수를 뜻합니다.
    완성된 모델 정의 파일은 다음과 같습니다.

    models:
    - name: "GPT-NeoX"
      model_path: "/models/triton-deploy"
      service:
        start_command:
          - tritonserver
          - --model-repository=/models/triton-deploy
          - --disable-auto-complete-config
          - --log-verbose
          - "1"
        port: 8000
        health_check:
          path: /v2/health/ready
          max_retries: 3
          max_wait_time: 5
    

    모델 정의 파일에 대한 더 자세한 내용은 Backend.AI WebUI 문서 에서 확인하실 수 있습니다.

    이제 Model Service를 실행하기 위한 모든 준비가 완료되었습니다.

    Model Service 생성

    1. "모델 서빙" 탭으로 이동합니다. "서비스 시작" 버튼을 클릭하여 Model Service 생성 창을 엽니다. 각 섹션에 대해 조금 더 상세히 살펴보겠습니다.
      • 서비스 이름: Model Service 이름을 지정하는 칸입니다. Model Service의 이름은 Model Service Endpoint의 Subdomain으로 사용될 수 있습니다 (추후 업데이트 예정).
      • 자원 그룹: Model Service용 Inference Session이 생성 될 자원 그룹을 선택하는 칸입니다.
      • 앱을 외부에 공개: 이 기능이 활성화 될 경우, 모델 서버로 향하는 모든 API 요청은 인증 헤더를 첨부해야 이루어질 수 있습니다. Model Service 인증에 대한 자세한 내용은 Backend.AI WebUI 문서 를 참고하세요.
      • 원하는 라우팅 수: 모델 서버 프로세스가 실행되는 추론 세션 수를 지정하는 칸입니다. 이 값을 1보다 큰 숫자로 설정할 경우 여러 개의 동일한 세션이 생성되고, API 요청은 이 세션들에게 균등하게 분배하는 라운드-로빈 로드 밸런서 기능이 활성화 됩니다. 이 값은 Model Service 생성 이후 언제든지 수정 가능합니다.
      • 추론 세션의 자원량을 지정하는 패널입니다.

    GPT-NeoX 20B 모델은 구동에 최소 40GB 이상의 vRAM을 요구합니다.
    Backend.AI의 fGPU 단위와 vRAM의 관계는 사용 중인 Backend.AI의 설정에 따라 다르게 적용될 수 있습니다. 자세한 사항은 사용 중인 Backend.AI 의 관리자와 상의하세요.

    모든 값을 올바르게 설정했다면 "확인" 버튼을 눌러 Model Service를 생성합니다.
    2. Model Service가 생성되었습니다. 추론 세션의 모델 프로세스가 아직 준비되지 않은 Model Service의 경우 상태가 "PROVISIONING" 에 머무르게 됩니다. "세션" 탭의 "INFERENCE" 섹션을 클릭하면 1에서 생성한 Model Service에 해당하는 추론 세션이 생성되었음을 확인할 수 있습니다. Model Service 관리자는 "제어" 행의 클립보드 아이콘을 클릭하여 추론 세션의 모델 서버 프로세스에 관련된 로그를 확인할 수 있습니다. 3. 모델 서버 프로세스가 정상적으로 실행되면 하단의 라우트의 상태와 상단의 상태가 모두 "HEALTHY" 로 변경되며, "서비스 엔드포인트" 에 Model Service에 접근하기 위한 주소가 나타납니다. 이제 해당 주소를 통해 추론 세션에 실행된 Triton Inference Server에 접근할 수 있습니다.

    마치며

    지금까지 Backend.AI Model Service를 이용해서 LLM 모델 서빙을 시작하는 방법에 대해 알아보았습니다. Model Service 기능은 Backend.AI의 Cloud Beta에서 사용 가능합니다. 지금 여러분만의 모델 서빙을 시작해 보세요!

    1: Backend.AI Model Service에서는 지원하지 않음

    21 November 2023

  • 23.09: 2023년 9월 업데이트

    By 래블업 주식회사

    23.09: 2023년 9월 업데이트

    2023년 하반기 Backend.AI의 주요 릴리즈인 23.09 가 출시되었습니다. 23.09 에서는 생성AI 의 개발, 파인튜닝 및 운영 자동화 기능이 대폭 강화되었습니다. 워크로드에 따라 자동으로 AI 모델을 스케일링, 로드밸런싱하고 다양한 GPU/NPU 지원을 확장하였으며 한 대의 노드는 물론 100~2천대 이상의 노드들을 관리할 때의 안정성도 증가하였습니다. 개발팀은 최선을 다해 마지막 비트까지 짜내기 위해 노력하고 있습니다. 지난 23.03 7월 업데이트 이후 개선된 주요 내용들은 다음과 같습니다.

    Backend.AI Core & UI

    • Backend.AI 모델 서비스 (Model Service) 기능이 정식 출시되었습니다. 이제 LLM과 같은 거대 모델의 학습 뿐만 아니라 추론 서비스를 위한 환경까지 Backend.AI 를 통해 더욱 효율적으로 준비할 수 있습니다. 자세한 내용은 Backend.AI Model Service 미리 보기 블로그를 참고하시기 바랍니다.
    • OpenID SSO(Single Sign-On) 를 사용하여 Backend.AI에 로그인 할 수 있는 기능이 추가되었습니다.
    • 커널 이미지가 지원하는 경우, 컴퓨팅 세션에서 sudo 명령을 패스워드 없이 사용하도록 설정 가능합니다.
    • HAProxy를 이용하지 않은 Redis Sentinel 를 지원합니다. 이를 테스트하기 위해 install-dev.sh 파일에 --configure-ha 설정을 추가했습니다.
    • Backend.AI Manager 와 Agent 간의 RPC 채널을 인증 및 암호화 통신이 가능하도록 기능을 추가하였습니다.
    • Backend.AI Manager 의 CLI 로그 기능이 개선되었습니다.
    • Backend.AI Agent 가 NAT 환경 아래에 놓여 있을 경우 Manager 가 RPC 연결을 할 수 없는 문제를 수정하였습니다.
    • Raft 알고리즘 라이브러리인 riteraft-py가 앞으로 raftify 로 개명되어 개발됩니다.
    • 다음의 신규 Storage Backend 들을 지원합니다.
      • VAST Data
      • KT Cloud NAS (Enterprise 전용)

    Backend.AI FastTrack

    • 다양한 이기종 가속기 지원을 위한 UI 를 개선하였습니다.
    • 이제 VFolder 삭제시 저장소 이름이 아닌 독립된 고유 ID 값을 이용합니다.
    • Django 버전이 4.2.5 로, Node.js 버전이 20 으로 업그레이드 되었습니다.
    • 미리 설정된 형태로 파이프라인을 생성해주는 파이프라인 템플릿 기능이 추가되었습니다.
    • 파이프라인 전용 폴더가 삭제되었을 경우 FastTrack UI상에 비활성화된 것으로 표시됩니다.
    • 파이프라인 삭제 과정이 개선되었습니다.
    • 태스크(세션)별 접근 가능한 BACKENDAI_PIPELINE_TASK_ID 환경변수가 추가되었습니다.
    • 태스크(세션)별 실제 실행 시간이 표시됩니다.

    Contribution Academy

    특별히 지난 기간에는 NIPA 에서 주관하는 2023 오픈소스 컨트리뷰선 아카데미를 통해 주니어 개발자 멘티분들의 다음과 같은 코드 기여들이 있었습니다.

    • SSH/SFTP 접속 예제를 클립보드에 복사하는 버튼을 생성하였습니다.
    • 기존에 사용되던 WebUI의 여러 Lit 엘리멘트를 React 로 리팩토링 하였습니다.
    • 다양한 테스트 코드들을 작성했습니다.
    • 정상적으로 동작하지 않던 환경변수 및 메시지 오류들을 발견하고 수정했습니다.

    Backend.AI 는 하루가 다르게 변하는 AI 생태계에서 다양한 환경을 지원함과 동시에 더욱 강력하고 사용자 친화적인 환경을 제공하고자 끊임없이 발전하고 있습니다. 앞으로의 행보에도 많은 기대 부탁드립니다!
    Make your AI accessible with Backend.AI!

    26 September 2023

  • 23.03: 2023년 7월 업데이트

    By 래블업 주식회사

    23.03: 2023년 7월 업데이트

    Backend.AI 23.03 및 22.09의 지속적 업데이트 내용을 정리합니다. 개발팀은 최선을 다해 마지막 비트까지 짜내기 위해 노력하고 있습니다.

    이번 업데이트에서 가장 중요한 변경사항은 다음과 같습니다:

    • 스토리지 관리성 강화: VFolder v3 아키텍처 적용으로 사용자 및 프로젝트별 스토리지 용량 관리 기능(Quota)을 추가하였습니다.
    • NVIDIA 호환성 확장: CUDA v12 및 NVIDIA H100 시리즈를 지원합니다.
    • 하드웨어 호환성 확장: 퓨리오사에이아이(FuriosaAI) 사의 WARBOY 가속기를 지원합니다.

    Backend.AI Core & UI

    • CUDA v12 및 NVIDIA H100 시리즈를 지원합니다.
    • 퓨리오사에이아이(FuriosaAI) 사의 첫 번째 NPU인 WARBOY 가속기를 지원합니다.
    • VFolder v3 아키텍처 적용으로 사용자 및 프로젝트별 스토리지 용량 관리 기능(Quota)을 추가하였습니다.
      • 단, Directory Quota를 지원하는 스토리지에 한정합니다.
    • 멀티노드 클러스터 세션 생성이 실패하는 오류를 수정하였습니다.
    • PULLING 상태의 연산 세션이 PREPARING 상태로 잘못 표기되는 오류를 수정하였습니다.
    • 다수의 스토리지에 동일한 이름의 폴더를 보유할 경우 해당 이름의 데이터 폴더 복제 시 CLONING 상태가 제대로 표기되지 않는 오류를 수정하였습니다.
    • 커널 이미지에 zsh 패키지가 설치되어 있는 경우 연산 세션의 웹 터미널이 zsh를 기본 쉘로 사용하도록 개선하였습니다.
    • (관리) 스토리지 프록시 및 이벤트 버스의 건강 상태를 알 수 있는 기능을 추가하였습니다.

    Backend.AI FastTrack

    • 태스크별 multi-node 클러스터 모드 설정 기능을 추가하였습니다.
    • .env에 설정한 환경변수가 프론트엔드에 적용되지 않는 오류를 수정하였습니다.
    • 모바일 브라우저로 접속할 경우 out-of-date로 인식하는 오류를 수정하였습니다.
    • 태스크별 에러가 발생할 경우 원인 메시지를 보여주는 필드를 추가하였습니다.
    • 기타 에디터 관련 문제를 개선하였습니다.

    Backend.AI 는 하루가 다르게 변하는 AI 생태계에서 다양한 환경을 지원함과 동시에 더욱 강력하고 사용자 친화적인 환경을 제공하고자 끊임없이 발전하고 있습니다. 앞으로의 행보에도 많은 기대 부탁드립니다!
    Make your AI accessible with Backend.AI!

    31 July 2023

  • bitsandbytes 이슈 삽질기

    By 강정석

    바야흐로 LLM(Large Language Model, 대형 언어 모델)의 시대입니다. 2022년 11월, OpenAI가 발표한 ChatGPTAlphaGo의 자리를 이어받아 현대 인공지능의 대명사가 되었습니다. 많은 기업과 연구소에서는 ChatGPT 기반의 자체적인 언어 모델을 개발하는 데 힘을 쏟고 있으며, Meta AI의 Llama 2와 같이 오픈 소스로 공개되는 사례도 증가하고 있어 개인의 접근성도 높아지고 있습니다.

    Backend.AI는 대규모 클러스터 운용과 분산처리에 편리성을 제공하고 있어 이러한 LLM을 개발하기 위한 환경으로써 많은 선택을 받고 있습니다. 실제로 다양한 고객사로부터 관련 피드백과 요청을 받고 있으며, 오늘은 그중에 하나를 해결한 과정을 다뤄보고자 합니다.

    2023년 4월 4일, NGC Catalog[^1](NVIDIA GPU Cloud)에서 제공하는 컨테이너 환경에서 특정 패키지를 실행할 때 에러가 발생한다는 이슈를 전달받았습니다. NGC Catalog는 AI/ML, 메타버스, 그리고 고성능 컴퓨팅 어플리케이션을 개발하기 위해 최적화된 환경이 구성된 컨테이너 목록[^2]으로, NVIDIA에서 직접 운영하고 배포하기 때문에 높은 신뢰도를 얻고 있으며 특히 CUDA 환경에서의 표준으로 여겨지고 있습니다. 따라서 해당 환경에서 문제가 발생한다는 것은 앞으로도 다수의 사용자가 마주하게 될 잠재적 위험을 안고 간다는 의미이기에 높은 우선순위로 이 이슈를 해결하기로 했습니다.

    문제 재현

    우선 정확한 원인을 파악하기 위하여 문제를 재현하는 과정을 먼저 거쳤습니다. 이번 사례는 Columbia University에서 개발한 ViperGPT[^3]를 실행하던 중 bitsandbytes라는 패키지에서 에러가 발생한 경우였습니다. ViperGPT는 아래와 같이 bitsandbytes에 의존성을 갖고 있습니다.

    accelerate==0.18.0
    backoff==2.2.1
    // highlight-next-line
    bitsandbytes==0.38.1
    cityscapesscripts==2.2.1
    git+https://github.com/openai/CLIP.git
    decord==0.6.0
    dill==0.3.6
    ...
    

    단순히 bitsandbytesimport 하는 것만으로 문제 재현이 가능했습니다.

    실행 환경은 nvcr.io/nvidia/pytorch:22.05-py3 이미지를 이용했습니다.

    $ pip install bitsandbytes  # 0.37.1
    $ python
    >> import bitsandbytes
    ===================================BUG REPORT===================================
    Welcome to bitsandbytes. For bug reports, please submit your error trace to: https://github.com/TimDettmers/bitsandbytes/issues
    ================================================================================
    CUDA exception! Error code: OS call failed or operation not supported on this OS
    CUDA exception! Error code: initialization error
    CUDA SETUP: CUDA runtime path found: /home/work/data/miniconda3/envs/vipergpt/lib/libcudart.so
    /home/work/data/miniconda3/envs/vipergpt/lib/python3.10/site-packages/bitsandbytes/cuda_setup/main.py:136: UserWarning: WARNING: No GPU detected! Check your CUDA paths. Proceeding to load CPU-only library...
      warn(msg)
    CUDA SETUP: Detected CUDA version 116
    CUDA SETUP: Loading binary /home/work/data/miniconda3/envs/vipergpt/lib/python3.10/site-packages/bitsandbytes/libbitsandbytes_cpu.so...
    /home/work/data/miniconda3/envs/vipergpt/lib/python3.10/site-packages/bitsandbytes/cextension.py:31: UserWarning: The installed version of bitsandbytes was compiled without GPU support. 8-bit optimizers and GPU quantization are unavailable.
      warn("The installed version of bitsandbytes was compiled without GPU support. "
    

    bitsandbytes는 실행 환경에 설치된 모든 CUDA 디바이스를 순회하며 Compute Capability[^4]를 확인합니다. 이때 아래와 같은 방식으로 libcuda.so를 이용하여 실행 환경에 설치된 CUDA 디바이스의 개수를 확인하도록 되어 있었습니다. 그중 cuDeviceGetCount()[^5]를 호출할 때 에러가 발생하는 것을 확인했습니다. 바로 304 CUDA_ERROR_OPERATING_SYSTEM 에러였습니다.

    def get_compute_capabilities(cuda):
        """
        1. find libcuda.so library (GPU driver) (/usr/lib)
           init_device -> init variables -> call function by reference
        2. call extern C function to determine CC
           (https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__DEVICE__DEPRECATED.html)
        3. Check for CUDA errors
           https://stackoverflow.com/questions/14038589/what-is-the-canonical-way-to-check-for-errors-using-the-cuda-runtime-api
        # bits taken from https://gist.github.com/f0k/63a664160d016a491b2cbea15913d549
        """
    
        nGpus = ct.c_int()
        cc_major = ct.c_int()
        cc_minor = ct.c_int()
    
        device = ct.c_int()
    
        # highlight-next-line
        check_cuda_result(cuda, cuda.cuDeviceGetCount(ct.byref(nGpus)))
        ccs = []
        for i in range(nGpus.value):
            check_cuda_result(cuda, cuda.cuDeviceGet(ct.byref(device), i))
            ref_major = ct.byref(cc_major)
            ref_minor = ct.byref(cc_minor)
            # 2. call extern C function to determine CC
            check_cuda_result(cuda, cuda.cuDeviceComputeCapability(ref_major, ref_minor, device))
            ccs.append(f"{cc_major.value}.{cc_minor.value}")
    
        return ccs
    

    bitsandbytes란?

    Transformer의 등장 이래로 언어 모델은 높은 성능 향상을 보였고, 더 많은 Transformer 블록을 쌓아 모델의 규모를 키우는 것이 트렌드가 되었습니다. 이로 인해 모델을 학습시키는 것뿐만 아니라 서비스할 때마저 수많은 GPU 자원을 요구하게 되었습니다. 예를 들어, 175B의 파라미터를 가지고 있는 GPT-3를 서비스하기 위해서는 약 $15,000의 80GB A100 GPU가 8개 필요합니다. 총 $120,000의 비용이 요구된다는 의미입니다. 이것은 개인뿐만 아니라 기업 혹은 연구소에도 큰 부담이 될 수밖에 없고, 이에 따라 서비스를 위한 추론 모델을 경량화하는 연구가 활발하게 진행되고 있습니다.

    이미지 출처: A Gentle Introduction to 8-bit Matrix Multiplication for transformers at scale using Hugging Face Transformers, Accelerate and bitsandbytes (Hugging Face)

    bitsandbytes는 University of Washington의 박사과정 Tim Dettmers가 Facebook AI Research(現 Meta AI)와 함께한 연구인 LLM.int8()[^6]를 오픈 소스로 공개한 것입니다. 행렬 곱을 연산할 때 각 벡터를 독립적으로 처리하는 Vector-wise Quantization 방법을 적용하고, 중요한 벡터는 16-bit로 표현하여 손실을 최소화하는 등 8-bit와 16-bit를 혼용하는 기법을 통해 모델의 성능은 유지하면서 크기를 줄이는 성과를 보였습니다. Hugging Face의 Transformer 구현체에도 병합이 되었으며, Llama2, QLoRA, KoAlpaca, 그리고 KULLM 등의 다양한 모델에서 사용되고 있습니다.

    원인 파악

    문제가 발생하는 지점을 찾아 재현까지 완료하였으니 이제 본격적으로 원인을 파악해야 합니다. 비슷한 사례가 있을지 조사해 봤으나 찾아볼 수 없었습니다. 또한 cuInit()은 정상적으로 호출되었기 때문에 더 원인을 파악하기 어려웠습니다.

    import ctypes
    
    count = ctypes.c_int()
    
    libcuda = ctypes.CDLL("libcuda.so")
    libcuda.cuInit(0)  # 0 (CUDA_SUCCESS)
    libcuda.cuDeviceGetCount(ctypes.byref(count))  # 304 (CUDA_ERROR_OPERATING_SYSTEM)
    
    libcudart = ctypes.CDLL("libcudart.so")
    libcudart.cudaGetDeviceCount(ctypes.byref(count))  # 304 (CUDA_ERROR_OPERATING_SYSTEM)
    

    조언을 얻기 위하여 아래와 같이 GitHub 레포지토리에 이슈(TimDettmers/bitsandbytes#264)를 등록했고, 패키지를 최신 버전으로 업데이트한 후 다시 시도해 보라는 답을 받을 수 있었습니다. 당시 최신이었던 0.38.0.post1 버전으로 올린 후 다시 테스트했지만 동일한 문제가 발생했습니다. 시간을 너무 지체할 수 없었기 때문에 생각을 전환하여 문제가 되는 부분을 제거하기로 했습니다.

    이미지 출처: 만화로 보는 그리스 로마 신화 (가나출판사)

    문제 해결

    첫 번째 접근은 CUDA-Python[^7]을 사용하는 것이었습니다. CUDA-Python은 NVIDIA에서 공식적으로 배포하는 CUDA Python Low-Level Bindings 패키지입니다. 이전에도 유용하게 사용한 경험이 있어서 바로 떠올릴 수 있었고, 바로 설치 및 테스트를 해보기로 했습니다.

    $ pip install cuda-python
    
    from cuda import cuda
    from cuda import cudart
    
    cuda.cuInit(0)  # (<CUresult.CUDA_SUCCESS: 0>,)
    cudart.cudaGetDeviceCount()  # (<cudaError_t.cudaSuccess: 0>, 1)
    

    다행히 cudart.cudaGetDeviceCount()가 정상적으로 동작하였고 곧바로 bitsandbytes에 통합하는 테스트를 진행했습니다. 하지만 cuda.cuInit(0)를 호출한 후 torch.cuda.is_available()을 호출하면 에러가 발생했습니다. torch.cuda.is_available() 내부에서 cudaGetDeviceCount()를 호출했기 때문입니다.

    from cuda import cuda, cudart
    
    cuda.cuInit(0)  # <CUresult.CUDA_SUCCESS: 0>,)
    cuda.cudaGetDeviceCount()  # (<cudaError_t.cudaSuccess: 0>, 1)
    
    import bitsandbytes
    
    # ...
    # /opt/conda/lib/python3.8/site-packages/torch/cuda/__init__.py:82: UserWarning: CUDA initialization: Unexpected error from cudaGetDeviceCount(). Did you run some cuda functions before calling NumCudaDevices() that might have already set an error? Error 304: OS call failed or operation not supported on this OS (Triggered internally at /opt/pytorch/pytorch/c10/cuda/CUDAFunctions.cpp:109.)
    #   return torch._C._cuda_getDeviceCount() > 0
    # ...
    

    문제는 다시 원점으로 돌아온 것 같았습니다. 숨을 한번 고르고 위의 에러 로그를 차분하게 다시 읽었습니다. 그러자 무언가 눈에 들어왔습니다.

    torch._C._cuda_getDeviceCount() > 0

    bitsandbytes는 이미 내부적으로 PyTorch를 사용하고 있었습니다. 즉, PyTorch에 대한 의존성을 가지고 있었습니다. 정확히는 bitsandbytes의존성을 갖는 lion-pytorch가 PyTorch에 대한 의존성을 가지고 있었습니다. 그리고 PyTorch에는 이미 CUDA 함수들에 대한 인터페이스가 존재했습니다. 이번에는 이걸 이용해 보기로 했습니다.

    다행히 PyTorch에는 bitsandbytes에서 사용하는 CUDA 함수들이 모두 존재했습니다. 기존에 libcuda.solibcudart.so를 통해 호출되던 함수들을 아래와 같이 변경했습니다.

    libcuda/libcudarttorch
    libcuda.cuDeviceGetCount()torch.cuda.device_count()
    libcuda.cuDeviceGet()torch.cuda.device()
    libcuda.cuDeviceComputeCapability()torch.cuda.get_device_capability()
    libcudart.cudaRuntimeGetVersion()torch.version.cuda

    변경 후 정상적으로 동작하는 것을 확인한 후, 배포 패키지 버전에 적용하기 위하여 GitHub 레포지토리에 PR을 등록했습니다(TimDettmers/bitsandbytes#375).

    후기

    PR을 등록한 지 약 두 달이 지난 2023년 7월 14일, 해당 패치가 main 브랜치에 병합되었고 0.40.1 버전에 포함되었습니다.

    또한 저자인 Tim Dettmers로부터 피드백을 얻을 수 있었습니다. 이 짧은 글에서 저자의 생각과 철학을 느낄 수 있었습니다.

    이번 기회를 통해 LLM의 생태계에 대해서 더 자세하게 알아볼 수 있었습니다. 또한 오랜만에 오픈 소스 활동의 재미를 느낄 수 있는 시간이었습니다. 공간적 제약을 뛰어넘어 협업할 수 있다는 점, 그리고 서로의 생각을 나누며 배워갈 수 있다는 점이 오픈 소스 활동의 매력인 것 같습니다. Backend.AI는 엔터프라이즈 버전과 함께 오픈 소스 버전을 운영하고 있습니다. 항상 더 나은 사용자 경험, 그리고 더 나은 개발자 경험을 제공할 수 있도록 노력하겠습니다.

    [^1]: NVIDIA GPU Cloud [^2]: The NGC catalog hosts containers for AI/ML, metaverse, and HPC applications and are performance-optimized, tested, and ready to deploy on GPU-powered on-prem, cloud, and edge systems. [^3]: ViperGPT: Visual Inference via Python Execution for Reasoning, March 14, 2023. [^4]: https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capability [^5]: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__DEVICE.html#group__CUDA__DEVICE_1g52b5ce05cb8c5fb6831b2c0ff2887c74 [^6]: LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale, November 10, 2022. [^7]: https://developer.nvidia.com/cuda-python

    28 July 2023

  • 23.03: 2023년 5월 업데이트

    By 래블업 주식회사

    Backend.AI 23.03 및 22.09의 지속적 업데이트 내용을 정리합니다. 개발팀은 최선을 다해 마지막 비트까지 짜내기 위해 노력하고 있습니다.

    이번 업데이트에서 가장 중요한 변경사항은 다음과 같습니다:

    • 하드웨어 호환성 확장: 리벨리온(Rebeillons) 사의 ATOM 가속기 유휴 상태 검사 및 Dell EMC 스토리지 백엔드를 지원하여 하드웨어 호환성을 확장했습니다.
    • 고속 업로드 강화: SFTP 기능을 도입하여 스토리지로의 고속 업로드를 지원합니다.
    • 개발환경 향상: 로컬 Visual Studio Code에서 원격 SSH 모드로 세션을 접속할 수 있도록 하여 개발환경을 향상했습니다.
    • 관리 용이성 증가: 관리자용 사용자 인터페이스 개선으로 AI 가속기 설정 및 자원 그룹 관리 용이성을 높였습니다.

    Backend.AI Core & UI

    • ATOM 가속기의 유휴 상태 검사를 지원하도록 추가하였습니다.
    • 스토리지로 직접 고속 업로드를 지원하는 SFTP 기능을 도입하였습니다.
    • 관리자 설정에 따라 주기적으로 비밀번호 업데이트를 강제 실행하는 기능을 추가하였습니다.
    • 업로드 전용 세션(SYSTEM) 탭을 추가하였습니다.
    • 허용되는 세션 타입에 Inference 타입을 추가하였습니다.
    • 로컬 Visual Studio Code에서 원격 SSH 모드로 세션 접속 기능을 추가하였습니다.
    • 폴더 탐색기에서 폴더 업로드를 지원하도록 하였습니다.
    • 세션 생성시 할당된 공유자원(shared memory)양을 표시하도록 개선하였습니다.
    • Dell EMC 스토리지 백엔드 지원을 추가하였습니다.
    • 컨테이너 메모리 사용량 측정의 정확도를 개선하였습니다.
    • 하나의 연산 노드에서 여러 개의 agent를 동시 실행할 수 있도록 개선하였습니다.
    • 관리자용으로 프로젝트/자원 그룹명 필터를 추가하였습니다.
    • 관리자용으로 GPU를 포함한 다양한 AI 가속기를 자원 프리셋/정책에서 설정할 수 있도록 사용자 인터페이스를 추가하였습니다.
    • 관리자용으로 GPU를 포함한 다양한 가속기의 할당과 현재 사용량을 표시하는 인터페이스를 제공하였습니다.
    • 관리자용으로 자원 그룹의 공개 여부를 설정할 수 있는 사용자 인터페이스를 제공하였습니다.
    • 관리자용으로 세션 별 idle-checks 값 확인할 수 있는 사용자 인터페이스를 제공하였습니다.
    • CLI에서 vfolder 업로드 시 재귀 옵션을 추가하였고, 상대 경로 처리를 개선하였습니다.
    • CLI에서 특정 세션 종료 시 의존성이 걸려있는 세션을 한번에 종료할 수 있는 재귀 옵션을 추가하였습니다.
    • 개발자용으로 기존의 cuda-mock 플러그인을 대체하는 새로운 mock-accelerator 플러그인을 추가하였습니다.
    • 개발자용으로 스토리지 프록시의 내부 모니터링을 위한 상태 및 통계 확인 API를 추가하였습니다.

    Backend.AI FastTrack

    • 파이프라인 모듈 추가 시 vfolder를 이름으로 검색할 수 있도록 개선하였습니다.
    • 파이프라인 실행 후 성공/실패를 쉽게 알 수 있도록 표시를 추가하였습니다.

    Backend.AI Forklift

    • 버그 수정 및 안정성을 개선하였습니다.
    • 빌드 작업 내역 삭제 기능을 지원합니다.
    • 빌드 작업 목록의 pagination을 지원합니다.

    Backend.AI 는 하루가 다르게 변하는 AI 생태계에서 다양한 환경을 지원함과 동시에 더욱 강력하고 사용자 친화적인 환경을 제공하고자 끊임없이 발전하고 있습니다. 앞으로의 행보에도 많은 기대 부탁드립니다!
    Make your AI accessible with Backend.AI!

    31 May 2023

  • Sneak Peek: Backend.AI Model Service 미리 보기

    By 조규진

    들어가며

    초거대 AI 모델들이 시장에 홍수처럼 쏟아지면서 모델을 개발하는 것 뿐만 아니라 어떻게 사용자에게 "잘", "효율적으로" 제공할 것이냐에 대한 고민이 늘어가고 있습니다. 거대 언어 모델 (Large Language Model, LLM) 이전의 AI 모델의 컴퓨팅 역량은 추론보다는 학습에 집중되었습니다. 학습이 완료된 모델으로 추론을 시도하기 위한 하드웨어 요구사항이 모델을 학습하는 데에 필요한 컴퓨팅 파워보다 월등히 작았기 때문입니다. 모델의 배포자는 실 사용자의 엔드 디바이스 (가령 스마트폰과 같은) 의 NPU 만으로도 추론을 위한 충분한 성능을 확보할 수 있었습니다. 그러나 LLM이 나타나며 상황이 역전되었습니다.

    Meta의 OPT 175b 를 예로 들어보겠습니다. OPT-175b는 이름에서 유추할 수 있듯 1750억 개의 파라미터를 보유하고 있으며, 추론 작업을 시행하기 위해 이를 GPU에 적재하는 데에만 대략 320GB 이상의 GPU 메모리를 필요로 합니다. LLM 이전에 유행했던 이미지 처리 계통의 모델들의 최대 요구치였던 4GB에 비하면 엄청나게 큰 차이입니다.
    AI 모델의 행태가 이렇게 변화하다 보니, 서비스 자원을 효율적으로 관리하는 것이 안정적으로 서비스를 운영하는 데에 무엇보다도 중요하게 작용하기 시작했습니다. 이번 글에서는 곧 출시될 Backend.AI의 모델 서비스 기능인 Backend.AI Model Service를 미리 살펴보며, Backend.AI를 이용하면 어떻게 AI 모델 훈련부터 서빙까지 하나의 인프라로 효율적으로 운용할 수 있을지에 대해 살펴보겠습니다.

    Backend.AI Model Service

    Backend.AI Model Service는 기존의 Backend.AI 솔루션 위에서 동작하는 모델 서빙 시스템입니다. 이미 많은 사례를 통해 안정성을 인정받은 Backend.AI의 컨테이너 관리 기술과 컨테이너 앱 제공 시스템인 AppProxy[^1]를 한 단계 더 고도화 하여, 추가적인 컴포넌트 설치 없이 기존의 Backend.AI 인프라 업그레이드만으로 하나의 인프라에서 AI 훈련과 모델 서비스를 모두 가능하게 합니다. 세션 별 GPU 사용량, API 호출 횟수 혹은 시간대 등에 따라서 자동으로 추론 세션의 규모를 확장 및 축소하는 오토 스케일링 기능 또한 지원하여 추론에 사용되는 AI 자원을 효과적으로 관리할 수 있습니다.

    추론 세션

    Backend.AI에서의 추론 세션은 기존의 훈련 세션과 개념적으로 동일합니다. 기존에 훈련을 위해 사용하던 실행 환경을 그대로 추론 세션에서 사용할 수도 있고, 추론 세션만을 위한 전용의 실행 환경을 배포할 수도 있습니다. 추론 세션은 휘발성이며 Stateless 하므로 세션의 상태가 좋지 않을 경우 언제든지 종료할 수 있습니다. 이 경우 Backend.AI에서는 새로운 추론 세션을 생성함으로써 원래의 상태를 복구하려고 시도함과 동시에 추론 요청을 다른 살아있는 추론 세션에게 전달하여서 추론 서비스의 Downtime을 최소화합니다.

    모델 스토리지

    Backend.AI를 통해 서비스를 제공할 모델들은 "모델 스토리지" 단위로 관리됩니다. 모델 스토리지는 모델 파일과 모델 서비스를 위한 코드, 그리고 모델 정의 파일로 이루어져 있습니다.

    모델 정의 파일

    모델 정의 파일은 서비스 제공자의 모델을 Backend.AI Model Service에서 실행하기 위한 정보를 정의하는 공간입니다. 모델 정의 파일에는 모델의 정보, 모델 서비스가 노출하는 포트, 모델 서비스를 실행하기 위해 실행해야 하는 일련의 작업들이 포함됩니다. 모델 서비스에서 자신의 상태를 보고하는 Health Check 기능을 제공할 경우, 해당 정보를 이용하여 불량 상태인 세션의 경우 서비스에서 제외하는 등의 조치가 가능합니다.

    models:
      - name: "KoAlpaca-5.8B-model"
        model_path: "/models/KoAlpaca-5.8B"
        service:
          pre_start_actions:
            - action: run_command
              args:
                command: ["pip3", "install", "-r", "/models/requirements.txt"]
          start_command:
            - uvicorn
            - --app-dir
            - /models
            - chatbot-api:app
            - --port
            - "8000"
            - --host
            - "0.0.0.0"
          port: 8000
          health_check:
            path: /health
            max_retries: 10
    

    다음은 잘 정의된 모델 정의 파일의 예시입니다. 이 예시는 KoAlpaca 5.8B 모델 을 모델 서비스로 실행하기 위한 일련의 과정을 담고 있습니다.

    튜토리얼: Backend.AI Model Service를 통해 모델 서비스 해 보기

    실제로 Backend.AI를 이용하여 이번 튜토리얼에서는 8bit로 양자화 된 KoAlpaca 5.8B 모델 을 서비스 하는 과정을 따라가 보겠습니다.

    API 서버 코드 작성

    모델을 제공하기 위한 간단한 API 서버를 작성합니다.

    import os
    from typing import Any, List
    
    from fastapi import FastAPI, Response
    from fastapi.responses import RedirectResponse, StreamingResponse, JSONResponse
    from fastapi.staticfiles import StaticFiles
    import numpy as np
    from pydantic import BaseModel
    import torch
    from transformers import pipeline, AutoModelForCausalLM
    import uvicorn
    
    URL = "localhost:8000"
    KOALPACA_MODEL = os.environ["BACKEND_MODEL_PATH"]
    
    torch.set_printoptions(precision=6)
    
    app = FastAPI()
    
    model = AutoModelForCausalLM.from_pretrained(
        KOALPACA_MODEL,
        device_map="auto",
        load_in_8bit=True,
    )
    
    
    pipe = pipeline(
        "text-generation",
        model=model,
        tokenizer=KOALPACA_MODEL,
    )
    
    
    class Message(BaseModel):
        role: str
        content: str
    
    
    class ChatRequest(BaseModel):
        messages: List[Message]
    
    
    BASE_CONTEXTS = [
        Message(role="맥락", content="KoAlpaca(코알파카)는 EleutherAI에서 개발한 Polyglot-ko 라는 한국어 모델을 기반으로, 자연어 처리 연구자 Beomi가 개발한 모델입니다."),
        Message(role="맥락", content="ChatKoAlpaca(챗코알파카)는 KoAlpaca를 채팅형으로 만든 것입니다."),
        Message(role="명령어", content="친절한 AI 챗봇인 ChatKoAlpaca 로서 답변을 합니다."),
        Message(role="명령어", content="인사에는 짧고 간단한 친절한 인사로 답하고, 아래 대화에 간단하고 짧게 답해주세요."),
    ]
    
    
    def preprocess_messages(messages: List[Message]) -> List[Message]:
        ...
    
    
    def flatten_messages(messages: List[Message]) -> str:
        ...
    
    
    def postprocess(answer: List[Any]) -> str:
        ...
    
    
    @app.post("/api/chat")
    async def chat(req: ChatRequest) -> StreamingResponse:
        messages = preprocess_messages(req.messages)
        conversation_history = flatten_messages(messages)
        ans = pipe(
            conversation_history,
            do_sample=True,
            max_new_tokens=512,
            temperature=0.7,
            top_p=0.9,
            return_full_text=False,
            eos_token_id=2,
        )
        msg = postprocess(ans)
    
        async def iterator():
            yield msg.strip().encode("utf-8")
    
        return StreamingResponse(iterator())
    
    
    @app.get("/health")
    async def health() -> Response:
        return JSONResponse(content={"healthy": True})
    
    
    @app.exception_handler(404)
    async def custom_404_handler(_, __):
        return RedirectResponse("/404.html")
    
    
    app.mount(
        "/",
        StaticFiles(directory=os.path.join(KOALPACA_MODEL, "..", "chatbot-ui"), html=True),
        name="html",
    )
    

    모델 정의 파일 작성

    API 서버에 맞추어 모델 정의 파일을 작성합니다.

    models:
      - name: "KoAlpaca-5.8B-model"
        model_path: "/models/KoAlpaca-Ployglot-5.8B"
        service:
          pre_start_actions:
            - action: run_command
              args:
                command: ["pip3", "install", "-r", "/models/requirements.txt"]
          start_command:
            - uvicorn
            - --app-dir
            - /models
            - chatbot-api:app
            - --port
            - "8000"
            - --host
            - "0.0.0.0"
          port: 8000
          health_check:
            path: /health
            max_retries: 10
    

    모델 서비스의 세션에서 모델 스토리지는 항상 /models 경로 아래에 탑재됩니다.

    모델 스토리지 준비

    작성한 모델 API 서버 코드와 모델 정의 파일, 그리고 KoAlpaca 모델을 모델 스토리지에 추가합니다.

    모델 서비스 생성

    모델 파일과 모델 정의 파일이 모두 준비가 되었다면 이제 Backend.AI Model Service를 시작할 수 있습니다. Model Service는 Backend.AI CLI의 backend.ai service create 명령을 통해 생성이 가능합니다. service create 가 허용하는 인자들은 backend.ai session create 명령과 거의 동일합니다. 사용할 이미지 뒤에는 모델 스토리지의 ID와 초기에 생성할 추론 세션의 갯수를 전달해 줍니다.

    backend.ai service info 를 이용하면 모델 서비스 및 서비스에 속한 추론 세션 상태를 확인할 수 있습니다. 1개의 추론 세션이 잘 생성되었음을 알 수 있습니다.

    추론 API 사용

    backend.ai service get-endpoint 명령을 이용하면 생성된 모델 서비스의 추론 엔드포인트를 확인할 수 있습니다. 추론 엔드포인트는 하나의 모델 서비스가 생성되고 제거되기 전까지 계속 고유한 값을 가집니다. 하나의 모델 서비스에 여러 개의 추론 세션이 속해 있을 경우 AppProxy는 여러 추론 세션에 요청을 분산합니다.

    추론 API 접근 제한

    추론 API에 접근 가능한 사용자를 제한하고자 하는 경우, --public 옵션을 제거한 채로 모델 서비스를 시작하면 추론 API에 인증 기능을 활성화할 수 있습니다. 인증 토큰은 backend.ai service generate-token 명령으로 발급할 수 있습니다.

    추론 세션 스케일링

    backend.ai service scale 명령을 이용하면 모델 서비스에 속한 추론 세션의 규모를 변경할 수 있습니다.

    마치며

    지금까지 Backend.AI Model Service와 Model Service 기능을 통해 실제로 모델 서비스를 배포하는 법에 대해 알아보았습니다. Backend.AI Model Service는 Backend.AI 23.03 버전에 정식 배포를 목표로 하고 있습니다. 빠른 시일 내에 Model Service 기능을 정식으로 선보일 수 있도록 노력하고 있으니 많은 기대 부탁드립니다.


    [^1]: Backend.AI Enterprise 부터 사용 가능.

    30 May 2023

  • 23.03: 2023년 3월 업데이트

    By 래블업 주식회사

    23.03: 2023년 3월 업데이트

    2023년을 맞이하여 Backend.AI의 첫 major 릴리즈인 23.03.0 버전이 발표되었습니다. 일부 기능은 후속 업데이트로 지속해서 선보일 예정입니다.

    특히 이번 업데이트에서는:

    • 새로운 연산 세션 유형으로 '추론(inference)' 서비스를 지원합니다.
    • 새로운 스토리지 폴더 유형으로 '모델(model)' 관리를 지원합니다.
    • 사용자 및 프로젝트 단위의 스토리지 용량 관리를 지원합니다.
    • FastTrack의 파이프라인 버전 관리 및 UI가 크게 개선되었습니다.

    Backend.AI Core & UI (23.03)

    • 모델 관리 및 추론 세션 관리 기능을 추가하였습니다.
      • 보다 고도화된 추론 엔드포인트 관리 및 네트워크 라우팅 계층은 후속 업데이트로 추가될 예정입니다.
    • 코드베이스가 Python 3.11 기반으로 업데이트되었습니다.
    • 프론트엔드에 React 구성요소들을 도입하고 Relay를 활용하여 보다 빠르고 반응성 높은 UI를 소개할 수 있는 기반을 준비하였습니다.
    • 설치 환경으로 Ubuntu 22.04부터 기본으로 사용하는 cgroup v2를 정식 지원합니다.
    • 사용자 및 프로젝트 단위의 스토리지 용량 관리를 위해 vfolder 구조를 v3로 업데이트하였습니다.
    • 커널과 세션을 이제 별도의 데이터베이스 테이블로 다루고, 상태 전이 추적 과정이 전반적으로 더 적은 데이터베이스 부하로 작동하도록 개선하였습니다.
    • 세션 실행 시 에이전트가 이미지 다운로드하는 과정의 진행상황을 표시하도록 개선하였습니다.
    • CUDA 11.7 이상 환경에서 컨테이너별 GPU 사용량 표시를 개선하였습니다.
    • 각 리소스그룹 내에서 사용자 및 프로젝트별로 스케줄링 우선순위(priority)를 지정할 수 있습니다.
    • 사용자 계정 보호를 위해 일회용 비밀번호(TOTP) 기반 2FA (이중인증, two-factor authentication) 로그인을 지원합니다.
    • 사용자가 직접 SSH keypair를 등록해 세션 접속이 가능하도록 지원합니다.
    • Graphcore IPU 와 Rebellions ATOM 장치에 대응하는 사용자 인터페이스를 지원합니다.

    Backend.AI Forklift (23.03)

    • Dockerfile 템플릿 및 고급 편집 기능을 추가하였습니다.
    • 추론용 컨테이너 이미지 작성을 지원합니다.
    • Harbor 레지스트리와 연동할 수 있도록 이미지 관리 기능을 확장하였습니다.

    Backend.AI FastTrack (23.03)

    • FastTrack UI 상에서 스토리지 폴더 내용을 바로 확인할 수 있습니다.
    • Core와의 세션 상태 동기화 방식을 이벤트 기반으로 개선하였습니다.
    • 파이프라인 스케줄의 최대 반복 횟수를 설정할 수 있습니다.
    • Task 실행에 실패할 경우 파이프라인 job이 대기하지 않고 자동으로 취소됩니다.
    • 파이프라인 버전 관리가 추가되었습니다. 파이프라인의 형상 이력을 추적할 수 있으며, 특정 시점의 내용을 불러와 이어서 작업을 진행할 수 있습니다.
    • 코드 에디터를 통해 YAML 형태의 파이프라인을 직접 수정할 수 있습니다.

    개발 및 연구 프레임워크 지원

    • TensorFlow 2.12, PyTorch 1.13 지원
    • NGC (NVIDIA GPU Cloud) TensorFlow 22.12 (tf2), NGC PyTorch 22.12, NGC Triton 22.08 지원
    • Google Colab과 동일한 라이브러리 및 패키지들을 제공하는 python-ff:23.01 이미지 추가

    위에 나열한 것 외에도 많은 버그 수정과 내부적인 개선 사항들이 포함되어 있습니다.
    앞으로도 더 많은 기능이 찾아올 예정입니다!

    31 March 2023

  • Concurrent React가 가져온 변화: 급하지 않은 렌더링 구분하기

    By 이종은

    Backend.AI의 MLOps 플랫폼인 FastTrack은 React 18을 사용하고 있습니다. React 18의 Concurrent 렌더러 덕분에 가능해진 급하지 않은 렌더링 구분하기에 대해 알아보겠습니다.

    React의 Concurrent 기능은 Async Rendering이라는 이름으로 JSConf Iceland 2018 에서 처음 외부에 공개된 이후에 2022년이 되어서야 정식 기능으로 React 18에 포함되었습니다. 이 기간에서 예상할 수 있듯이 Concurrent 렌더러는 React 18에서 가장 크고 중요한 변화에 해당합니다. 렌더러가 변경되었지만 React 개발자들은 큰 변경 없이 React 18 이전 버전에서 제작한 React 코드를 React 18에서 실행 가능합니다. 심지어 React의 Concurrent 렌더러를 모르더라도 React로 UI를 만들 수 있습니다. 하지만 이 Concurrent 렌더러가 무엇이고 어떤 상황에서 유용한지를 이해한다면 React로 개발할 때 복잡했던 머릿속이 간단명료해지고 보다 나은 UX를 제공하는 UI를 개발할 수 있습니다. 이 글에서는 Concurrent 렌더러가 내부적으로 어떻게 동작하는지에 대해 이야기하지 않습니다. React를 이용해서 애플리케이션을 만드는 개발자에게 더 중요하다고 할 수 있는 Concurrent 렌더러가 무엇이며 React 개발자가 사고하는 방식이 어떻게 바뀔 수 있는지에 대하여 살펴봅니다.

    이 글의 내용을 요약해 보자면 다음과 같습니다.

    Concurrent 렌더러 덕분에

    • 컴포넌트 렌더링은 중단될 수 있습니다.
    • 화면에 보이지 않는 곳에서 트리의 일부를 렌더링 할 수 있습니다.
    • 이로 인해 React 개발자가 이전과는 달리 급하지 않은 렌더링을 구분할 수 있습니다.

    “React 컴포넌트는 추상적으로 순수 함수이다.”

    React 컴포넌트는 실제로 자바스크립트 함수로 만듭니다.(클래스로 만드는 방법도 있지만 대부분의 경우 추천하지 않습니다.) 함수는 입력을 주면 출력을 만듭니다. 입력이 변하면 출력이 달라질 수 있으므로 함수를 실행해서 새로운 출력을 만듭니다. (순수 함수는 입력이 같으면 출력이 같습니다.)

    React 컴포넌트의 입력과 출력은 무엇인가요?
    React 컴포넌트의 입력은 해당 컴포넌트가 함수로서 받게 되는 property(React에서는 props라 함)이며 출력은 함수가 리턴하는 React 엘리먼트입니다.

    hook을 통한 state도 입력일까요?
    hook도 추상적으로 함수의 입력이라 할 수 있습니다. React props와 동일하게 값이 변하면 다시 렌더링하게 만드는 트리거(trigger)이며 이 변화를 통해 React 컴포넌트의 출력을 달라지게 합니다.

    자, 다시 렌더링에 대한 이야기로 돌아가겠습니다.

    컴포넌트 렌더링은 중단될 수 있다.

    Concurrent React의 핵심은 '렌더링은 중단될 수 있다.'입니다. React 18 이전에는 중단될 수 없었습니다(experimental 제외). React 컴포넌트가 함수로서 렌더링을 위해 실행되게 되면 return 하기 전까지 그 어떤 자바스크립트 연산도 실행할 수 없었습니다. 렌더링을 위한 함수의 실행이 오래 걸린다면 엘리먼트를 return하기 전까지 사용자의 클릭을 처리하는 이벤트 핸들러 함수를 실행할 수 없다는 얘기입니다. 하지만 18버전부터는 중단될 수 있습니다.

    const A = ({ count }) => {
      return (
        <div>
          <span>{count}</span>
          <B/>
          <C/>
        </div>
      );
    };
    
    const B = () => {
      const [text, setText] = useState("");
      return (
        <div>
          <input value={text} onChange={(e) => setText(e.target.value)} />
          <D/>
        </div>
      );
    };
    
    const C = () => {
      return <span>C</span>;
    };
    
    const D = ({ text }) => {
      verySlowFunction(text); //연산이 수 초 걸리는 함수라 가정합시다.
      return <span>D</span>;
    };
    

    React 18 이전 버전에서는 A를 렌더링하면 B와 C가 렌더링되어야 하고, B를 렌더링하려면 D가 렌더링이 되어야 했습니다. A의 렌더링을 시작하면 필요한 B, C, D 가 모두 렌더링된 후 A의 리턴 값인 React 엘리먼트를 리턴하기까지 다른 자바스크립트 연산을 수행할 수 없습니다. A에서 리턴하는 컴포넌트 트리가 한 덩어리처럼 렌더링 됩니다. A의 렌더링이 시작되면 A의 렌더링을 중간에 중단하는 건 불가능했습니다.

    Concurrent React에서는 렌더링을 중간에 중단할 수 있습니다. 렌더링을 중단하는 것은 왜 필요한 것일까요? 다음을 생각해 볼 수 있습니다.

    • 지금 진행 중인 렌더링이 더 이상 유효하지 않을 때(stale)
      • 예를 들어 위 코드에서 A의 count prop이 1인 상태로 렌더링 중인 상황을 생각해봅시다. 이 때 이 렌더링이 완료되기 전에 count가 2로 변해서 2일 때의 A에 대한 렌더링 요청이 발생했습니다. 그러면 1일 때의 렌더링 결과는 최신 값을 표시하는게 아니므로 더이상 필요없어 집니다. 이런 상황에서 바로 1일 때의 렌더링을 중단하고 2일 때의 렌더링을 시작할 수 있다면 더 빨리 사용자에게 최신값인 2일 때의 화면을 보여 줄 수 있게됩니다.
    • 진행 중인 렌더링이 보여주려는 화면의 갱신 보다 더 먼저 처리하고 싶은 것이 있을 때
      • 렌더링 중에 사용자 이벤트가 발생할 경우, 즉각적으로 반응하기 위하여 진행 중인 렌더링을 중단하고 이벤트 핸들러를 우선적으로 실행할 수 있습니다.

    이러한 경우들은 모두 해당 컴포넌트의 렌더링을 중단해서 다른 처리를 할 수 있게 함으로써 UX를 개선하는 경우입니다.

    화면에 보이지 않는 곳에서 트리의 일부를 렌더링 할 수 있다.

    Concurrent React에서는 화면에 보이는 것과 일치하는 렌더링 외에 화면의 일부에 해당하는 컴포넌트만 별도로 렌더링 할 수 있습니다. 이는 기존 렌더링을 화면에 보여주고 계속 동작하게 하면서 앞으로 갱신될 화면의 일부를 미리 별도로 렌더링하고 렌더링이 완료되면 교체하게 됩니다. 렌더링을 필요 이상으로 하기 때문에 사용성을 떨어트리게 되는 게 아닌가 하는 걱정이 들 수 있습니다. 하지만 이 별도의 랜더링은 Concurrent 렌더러 덕분에 언제든지 중단될 수 있으므로 사용자 인터렉션을 방해하지 않게 됩니다. 오히려 이 특징을 이용하여 보다 나은 UX를 제공할 수 있습니다.

    지금까지 Concurrent 렌더러의 두 가지 특징을 살펴봤습니다. 이번에는 이 두 특징이 활용되는 '급하지 않은 렌더링 구분하기'가 무엇인지 알아봅시다.

    급하지 않은 렌더링 구분하기

    급한 렌더링과 급하지 않은 렌더링 예시:
     

    브라우저를 통해 사이트에 처음 방문하는 것을 생각 해봅시다. 하얀 빈 화면이 있는 상황에서 가장 급한 것은 무엇인가요? 어떻게든 빨리 사이트의 내용을 화면에 뿌려주는 게 가장 중요한 일일 겁니다. 하얀 화면에 오래 머문다면 사용자는 기다리지 않고 떠날 수 있으니까요. 그러니 첫 번째 렌더링은 딱히 중단할 일이 없습니다.
     

    홈페이지의 좌측 사이드바를 보니 내비게이션에 해당하는 메뉴들이 있습니다. 메뉴 A를 누르려다가 메뉴 B를 잘못 눌렀습니다. 사용자가 다시 메뉴 A를 누르려고 하는데 메뉴 B 렌더링이 오래 걸린다면 메뉴 B에 해당하는 화면의 렌더링이 끝나고 메뉴 A에 해당하는 화면이 렌더링이 될 것입니다.
     

    메뉴 B를 눌렀다가 메뉴 A를 바로 다시 누른 상황에서 메뉴 B에 대한 화면을 렌더링하는 것보다 메뉴 A에 대한 화면을 렌더링하는 것이 더 급합니다. B에 대한 화면은 이제 유효하지 않습니다.

    React 개발자는 어떻게 어떤 렌더링이 급하지 않은 렌더링이라고 React에게 알릴 수 있을까요? 렌더를 트리거하는 입력의 변화 중에 어떤 입력의 변화가 급하지 않은지를 표시하면 됩니다. 이를 개발자가 손쉽게 표시할 수 있게 해주는 hook이 useDeferredValueuseTransition 입니다. 이 두 API 모두 리액트 18에 새롭게 추가되었으며 급하지 않은 렌더링을 지연시키는 동일한 효과를 가져옵니다. 이 두 hook을 하나씩 살펴보면서 이 둘의 차이점을 이해해 봅시다.

    useDeferredValue: 변한 입력값을 이용하여 구분하기

    특정 값을 사용하는 컴포넌트에서 해당 컴포넌트는 그 특정한 값의 변화를 급하지 않게 처리하고자 할 때 사용합니다.

    function App() {
      const [text, setText] = useState('');
      return (
        <>
          <input value={text} onChange={e => setText(e.target.value)} />
          <SlowList text={text} />
        </>
      );
    }
    

    위 예시 코드는 beta.reactjs.org의 useDeferredValue 예제 중 하나입니다.

    여기서 text는 state이므로 text가 변하면 App이 다시 렌더링됩니다. 이 text<input><SlowList>의 입력(props)으로도 사용되고 있습니다. text가 변하면 App의 렌더링이 트리거가 되고 그 과정에서 inputSlowList가 바뀐 text를 사용하여 다시 렌더링됩니다. SlowList의 렌더링이 오래 걸린다면 사용자가 빠르게 타이핑을 이어가도 매 렌더링이 완료되기까지 사용자의 입력이 반영되지 않습니다.

    여기서 inputSlowList에서 사용자의 키보드 입력에 해당하는 input의 렌더링은 급한 렌더링이고 SlowList는 사용자 입력에 따른 어떠한 결과이므로 input보다는 급하지 않은 렌더링이라고 볼 수 있습니다. 여기서 useDeferredValue를 사용하면 급한 렌더링을 트리거하는 text를 이용하여 급하지 않은 렌더링을 할 때 화면에 표시되는 deferredText를 만들 수 있습니다.

    function App() {
      const [text, setText] = useState('');
      const deferredText = useDeferredValue(text);
      return (
        <>
          <input value={text} onChange={e => setText(e.target.value)} />
          <SlowList text={deferredText} />
        </>
      );
    }
    

    이렇게 하게 되면 text의 변화가 있을 때 deferredText는 바로 이전 text값을 갖게 되지만 화면에 보이지 않는 곳에서 deferredText도 최신의 값을 갖는 상태로 별도의 렌더링을 하게 되고 이 별도의 렌더링이 완료되어야 비로소 textdeferredText 모두 최신의 값을 갖는 상태로 렌더링됩니다. deferredText의 변화는 급하지 않은 렌더링이고 중단될 수 있습니다.

    동일한 컴포넌트에 대해 급하지 않은 렌더링 요청이 연속적으로 있을 경우, 먼저 시작된 급하지 않은 렌더링이 끝나지 않았다면 바로 중단하고 최근 변화의 렌더링을 시작하게 됩니다. 예를 들어 text에서 사용자가 비어있는 input 상자에 ab를 순서대로 타이핑하게 되면 a가 입력되었을 때 별도의 렌더링이 시작되고, 이 렌더링 끝나기 전에 b를 눌러 ab가 되었다면 a일 때 시작된 별도의 렌더링은 중단하게 되고 ab를 위한 렌더링이 시작됩니다.

    useTransition: 입력의 변화를 주는 함수를 이용하여 구분하기

    앞서 useTransitionuseDeferredValue 모두 급하지 않은 렌더링으로 구분할 때 사용된다고 했습니다. 이 둘의 차이점을 살펴보면서 useTransition에 대해 알아봅시다.

    :warning: 주의

    이 둘의 차이를 쉽게 이해하기 위해 useDeferredValue의 예제를 useTransition을 사용하도록 변경했습니다. useTransition은 업데이트가 동기적으로 일어나야 하는 input과 사용할 수 없습니다. 사용할 수 없는 이유는 beta.reactjs.orguseDeferredValue 페이지의 Trouble shooting에서 확인하시기 바랍니다.

    function App() {
      const [text, setText] = useState("");
      const [isPending, startTransition] = useTransition();
      return (
        <>
          <button
            onClick={(e) => {
              startTransition(() => setText((v) => v + "a"));
            }}
          >
            a 키
          </button>
          <SlowList text={text} />
        </>
      );
    }
    

    차이점

    급하지 않은 렌더링이라고 지정할 때 useDeferredValue는 그 값인 text를 이용했다면 useDeferredValue는 그 값(렌더 트리거)의 변화를 야기하는 setText를 이용합니다. text를 접근할 수 없는 상황에서 setText만 알아도 됩니다.

    startTransition 안에서 변경되는 text의 변화를 바로 화면에 표시할 방법이 없습니다. 별도의 렌더링으로 변경된 text를 위한 렌더링이 시작되지만 별도의 렌더링일 뿐 실제 화면을 위한 렌더링에는 그 변경된 값을 알 수는 없습니다. 다만 isPending을 통해 별도의 렌더링이 진행 중임은 알 수 있습니다. useTransitionstate의 변경을 지연하게 되고 useDeferredValue은 변경된 state에 따른 일부 렌더링을 지연하게 됩니다.

    공통점

    startTransition을 통해 동일한 컴포넌트에 대해 급하지 않은 렌더링 요청이 연속적으로 있을 경우 useDeferredValue와 동일하게 먼저 시작된 별도의 렌더링이 아직 진행 중이라면 바로 중단되고 최신 값을 사용하는 별도의 렌더링이 시작됩니다.

    정리

    React 18의 Concurrent 렌더러 덕분에 가능해진 '급하지 않은 렌더링 구분하기'에 대해서 살펴보았습니다. useTransition과 useDeferredValue를 사용하여 급하지 않은 렌더링을 구분하게 되면 복잡한 구조의 화면 갱신도 사용성을 떨어뜨리지 않으면서 가능해집니다. React 18 이전에는 이러한 막힘없는 사용성을 제공하기 위해서는 많은 개발공수가 들었습니다. React 18에서 간편해진 '급하지 않은 렌더링 구분하기'를 통해 사용자에게 쾌적한 UX를 제공해 보시기 바랍니다.

    29 January 2023

  • Backend.AI MLOps 플랫폼 FastTrack을 소개합니다.

    By 강지현

    이번 글에서는 Backend.AI의 MLOps 플랫폼인 FastTrack을 소개합니다. FastTrack을 사용하면 데이터 전처리, 학습, 검증, 배포, 그리고 추론과 같은 각각의 단계를 하나의 파이프라인으로 구성할 수 있습니다. 특히 FastTrack에서는 파이프라인을 구성할 때에 사용자가 각 단계를 손쉽게 커스터마이징 가능합니다. 이번 포스팅에서는 MLOps 플랫폼이 왜 필요한지와 함께 Backend.AI FastTrack 의 탄생 배경, 그리고 FastTrack 이 가지는 특장점을 함께 소개합니다.

    MLOps 플랫폼의 대두

    지난 몇 년간 IT 산업 뿐만 아니라, 디지털 트랜스포메이션이 일어난 대부분의 산업에서는 AI를 도입해 산재되어 있던 데이터로 유의미한 예측을 도출해 빠르게 변하는 시장에 대응할 수 있도록 각고의 노력을 기울여왔습니다. 이 과정에서 AI를 잘 활용하기 위해서는 모델 학습, 최적화에서 끝나는 것이 아니라, 데이터 I/O를 고려한 하드웨어 도입, 모델 버전 관리 등과 같이 다양한 단계에 대한 대응이 필요하게 되었습니다. 여기서 나온 개념이 MLOps(Machine Learning Operations) 입니다. MLOps에 대한 자세한 내용은 래블업 기술 블로그에서 다루고 있는 MLOps 시리즈 에서 확인하실 수 있으니, FastTrack 소개글을 보기에 앞서 MLOps 개념이 생소하신 분들께서는 위의 글을 훑어보시는 것을 추천합니다.

    FastTrack의 역사

    래블업은 DevOps 파이프라인 수요에 대응하고자 2019년 Backend.AI 파이프라인 기능을 베타 릴리즈로 추가했습니다. 복잡한 파이프라인 생성 및 관리 과정을 단순화하고, 중간에 두 경로 이상으로 나누어지는 단방향 파이프라인을 운영하는 기능을 개발 및 테스트로 공개하였습니다. 그러나, MLOps 개념의 대두와 함께 AirFlow, MLFlow, KubeFlow 등의 다양한 파이프라인 솔루션들이 보급됨에 따라 저희는 파이프라인 기능을 정식 기능으로 개발하는 대신, 오픈소스 파이프라인 도구들을 통합하고 지원하는 쪽으로 개발 방향을 선회했습니다.

    한편 AI 개발 파이프라인은 점차 복잡해지고, 유저들의 다양한 요구들을 오픈소스 MLOps 파이프라인 도구들이 채워줄 수 없음이 명확해진 시점에서 저희는 Backend.AI의 파이프라인 기능을 다시 되살리기로 했습니다. Backend.AI 파이프라인 기능의 재활성화 및 프로토타이핑 과정에서, 유저들의 요청을 바로 반영할 수 있도록 본체에 완전히 통합된 파이프라인 대신 Backend.AI 클러스터와 함께 동작하지만 독립적으로 동작하는 MLOps 파이프라인 솔루션으로 개발 방향이 변경되었습니다.

    이렇게 다양한 역사를 밟아온 래블업의 AI/MLOps 솔루션은 공항이나 물류 등에서 통과 및 통관 절차를 빨리 처리해주는 과정을 부르는 FastTrack Lane에서 힌트를 얻은 FastTrack으로 명명하였으며, Backend.AI 22.09와 함께 첫 정식 버전을 테스트 중입니다.

    FastTrack이란?

    FastTrack 이란 Backend.AI 클러스터를 기반으로 여러 개의 작업단위들을 사용자가 목적에 맞게 커스터마이징 하고, DAG(Directed Acyclic Graph)형태로 실행될 수 있도록 돕는 머신러닝 워크플로우 플랫폼입니다. 머신러닝 파이프라인의 각 단계에 대응하는 세션을 선,후 관계를 통해 실행할 수 있게 되면 사용자는 데이터 전처리, 학습, 검증, 배포, 모니터링, 최적화 등과 같은 각 단계를 필요에 따라 결합해 하나의 워크플로우로 다룰 수 있습니다. 다시 말해 기존 Backend.AI 클러스터에서 사용자가 일일이 수동으로 생성해야 했던 세션을 워크플로우로 구성하여 단계가 끝날 때마다 자동으로 스케줄링 해주기 때문에 사용자는 보다 편리하게 모델을 구축, 재사용할 수 있습니다.

    FastTrack 구조와 특징

    FastTrack에서는 워크플로우 템플릿을 파이프라인(Pipeline), 실행 대상인 워크플로우를 파이프라인 잡(Pipeline Job)으로 구분하고, 워크플로우 안의 작업단위를 태스크(Task), 실행 대상인 작업단위를 태스크 인스턴스(Task instance)로 구분합니다. 아래의 구조도와 함께 FastTrack에서 어떻게 단계별 작업이 진행되는지 설명합니다.

    파이프라인(Pipeline)

    파이프라인은 태스크들의 각각의 정보와 관계를 모아둔 집합체로, DAG(Directed Acyclic Graph) 구조를 갖습니다. AI 워크플로우를 만들기 위해서는 파이프라인을 생성하면 되는데, 이 때 학습이 잘 되고 있는지 등을 아티팩트(artifact)로 확인할 수 있도록 FastTrack에서는 Backend.AI 클러스터에 파이프라인 전용 폴더를 자동생성합니다. 또한 FastTrack에서는 드래그-앤-드랍(Drag and drop)과 같은 인터페이스로 사용자가 손쉽게 태스크 간 관계를 수정할 수 있고, 변경 결과를 즉시 도식화된 플로우로 확인 및 YAML 파일로 확인할 수 있어 매우 편리합니다. 또한 파이프라인은 YAML 파일로 관리되기 때문에 내보내기나 불러오기가 용이하여 사용자간 공유도 손쉽게 할 수 있습니다.

    파이프라인 잡(Pipeline Job)

    파이프라인 잡의 경우 생성된 파이프라인 정보를 기반으로 만들어지는 실제 개체로, 실행이 되는 동안에는 수정이 불가하다는 특성을 갖습니다. FastTrack GUI에서는 작업단위가 실행되는 것을 각 작업단위에 대응하는 노드의 색상으로 확인할 수 있습니다. 또한 파이프라인과 마찬가지로, 구성하고 있는 태스크 인스턴스의 정보와 관계를 YAML 형태로 관리합니다. 모든 태스크 인스턴스가 종료되면, 파이프라인 잡의 상태도 성공 또는 실패로 표시됩니다.

    태스크(Task)

    파이프라인을 이루는 최소 실행단위로, 용도 별로 자원 할당이 가능합니다. 가령 모델 학습만을 위한 태스크의 경우, 전처리 용과 달리 많은 GPU 자원을 집중할당하여 자원을 보다 효율적으로 사용할 수 있습니다. 또한 실행환경도 각각 지정할 수 있습니다. Backend.AI 클러스터에서 지원하는 이미지를 기준으로 TensorFlow, PyTorch, Python 3.x, NGC TensorFlow, NGC PyTorch 등과 같은 이미지를 도커 빌드과정 없이 그대로 사용할 수 있습니다. 또한 필요에 따라 Backend.AI 클러스터에서 생성한 가상폴더(Virtual Folder)를 태스크 별로 마운트할 수 있습니다.

    태스크 인스턴스(Task Instance)

    태스크 인스턴스는 파이프라인 잡이 생성될 때 파이프라인을 구성하는 태스크 정보를 바탕으로 생성되는 실제 개체라고 볼 수 있습니다. 즉 AI 워크플로우를 실행하는 것은 파이프라인 잡을 구성하는 태스크 인스턴스가 지정된 선,후 관계에 맞게 실행이 된다는 것을 의미합니다. 태스크 인스턴스는 현재 Backend.AI 클러스터의 세션(Session)과 1:1 대응이 되어 세션 상태와 태스크 인스턴스의 상태가 동일시 되고 있으나, 추후 세션 외에도 다양한 실행 단위로 확장될 예정입니다.

    마치며

    지금까지 Backend.AI MLOps 플랫폼인 FastTrack에 대한 소개와 함께 MLOps 에 대해 다뤄보았습니다. 현재 Backend.AI FastTrack의 경우 22.09 버전이 릴리즈 되었으며, 추후 파이프라인 버저닝, 파이프라인 간 의존 관계 추가, 태스크 자원 사용 최적화, GitHub 기반 모델/데이터 스토어 지원 등과 같은 다양한 사용자 편의 기능을 개발 및 제공할 예정입니다. 누구나, 언제 어디서든 AI 모델을 개발, 사용할 수 있게 하자는 래블업의 모토에 맞게, FastTrack을 이용하면 누구나 손쉽게 자동화된 모델 구축을 할 수 있도록 만들어가겠습니다. 앞으로의 행보에도 많은 관심 부탁드립니다.

    29 November 2022

도움이 필요하신가요?

내용을 작성해 주시면 곧 연락 드리겠습니다.

문의하기

본사 및 HPC 연구소

서울특별시 강남구 선릉로 577 CR타워 8층

© Lablup Inc. All rights reserved.