Writing

On 2020 as researcher

On 2020 as researcher

올해로 1년 좀 넘게 음성 합성 리서치 업무를 보고 있다.
이번 글에선 19년 9월, 입사부터 20년 말까지 한 활동을 조심히 정리해보려 한다.

[대학교 3학년의 2020] On 2020 as student
[딥러닝 리서처의 2020] 현재 글

 

입사 당시

입사 당시 우리 회사는 설립된 지 반년 정도 된 학부생 스타트업이었다.

처음에는 대표님의 요청으로 비전 프로젝트 외주를 진행했었고,
9월에 입사하여 본격적으로 음성 업무를 보기 시작했다.

당시 회사 인원은 나 포함 5명이었고, 각자 역할이 부여된 상황이었다.
그중 나는 TTS 리서처로 들어왔다.

당연히 사수는 없었다.
TTS 연구 개발 프레임워크는 리팩토링이 시급해 보였고,
그를 기반으로 딥러닝 리서치를 진행하는 과정이 확립되어 있지 않았다.

모든 것을 처음부터 시작해야 하는 상황이었다.
하지만 이제 막 2학년이 끝난 학부생은 모든 게 패기로웠다.

 

딥러닝 리서처 업무

음성 합성 리서치의 목표는
합성된 음성의 발음이 또렷해야 하고, 자연스러우면서, 음질이 좋아야 한다.
이후에는 다국어, 다화자, 감정 등 추가 기능 지원이 들어간다.

논문 리뷰

회사 들어와서 가장 먼저 한 리서치 업무는
음성 합성 논문 리스트를 만들고 쭉 리뷰 한 것이었다.

처음 입사했을 때에는 푸리에 변환이 뭔지도 모르고 시작했다.
그냥 이렇게 저렇게 만든 스펙트로그램이란 피쳐가 있고,
딥러닝 모델이 텍스트에서 스펙트로그램으로의 매핑을 학습한다는 정도만 나이브하게 알고 있었다.

신호처리 공부도 해야 했고, 딥러닝 모델 논문도 봐야 했다.
그렇게 1년 동안 대략 60편 정도의 논문을 보고 20개 가까이 구현해본 것 같다.

입사 전에도 그랬지만, 시작부터 다독을 목표로 했다.
리뷰가 진행된 논문이던, 안 된 논문이던,
메이저 학회에 통과한 논문이던, 그저 arXiv에 올라온 글이던 가리지 않았다.

논문 볼 때 정리했던 내용

논문을 보면 항상 5가지 항목에 대해서 정리했다.

  1. 어느 분야에서
  2. 이전 논문들에서 어떤 문제점을 발견했고
  3. 어떤 해결방법을 제시했으며
  4. 이때 생기는 이점과
  5. 발견되었거나 예상되는 약점은 무엇인지

그렇게 해서 학계의 흐름을 쫓고 있고,
올해 말부터는 이제 본인만의 실험을 기획하기 시작했다.

딥러닝에 관심을 가지고 있던 적당한 학부생이었고,
어디서 연구하는 방법이란 것을 배워본 적이 없었기 때문에 모든 게 서툴렀다.

논문을 볼 때 어느 것이 중요하고,
어떻게 쫓아야 하고, 무엇을 이야기하고 싶은건지,
저 5가지 질문을 확립하는데에도 꽤 시간이 걸렸던 것 같다.

신호처리 관련 공부

문제는 어느 시점부터, 음성 분야 논문이 단순 스펙트로그램을 넘어서
여러 가지 피쳐나 기성 신호처리 알고리즘들을 차용하여 성능을 높이는 시도가 등장했다는 것이다.

따로 물리나 신호처리학을 공부해본 적이 없었기 때문에,
어디서부터 무엇을 공부해야 할지도 몰랐고,
처음부터 공부해서 이른 시일 안에 현업에 사용할 자신도 없었다.

그래서 모르는 단어가 나오면,
탑다운 방식으로 구글에 단어를 검색하고,
대학 강의에 쓰인 pdf 파일을 보면서 공부했다.

(생각보다 검색하면 잘 나옴)

(생각보다 검색하면 잘 나옴)

그러다 보니 틀리게 이해한 내용도 많았고,
이곳저곳 빈 곳도 많았다.

하지만 탑다운도 결국 수렴한다고,
결국에는 틀린 이해를 정정하고, 꽤 많은 빈 곳을 채웠다는 생각이 든다.

모델 구현

기존에는 tensorflow를 많이 활용했었다.
하지만 회사에서는 pytorch를 사용하고 있었고,
이에 맞추기 위해 하루에서 이틀 정도는 파이토치에 적응을 좀 했던 거 같다.

모델을 구현하기에 앞서 원저자가 구현한 오픈소스 코드가 있는지 확인했다.

처음에는 하루 정도면 모델 하나 구현할 수 있다고,
오픈소스 찾아볼 게 있냐고 처음부터 짜는 객기를 부렸지만,

딥러닝 코드는 버그를 잡기 어렵고,
이미 구현된 코드가 있으면 I/O 정도만 수정해서 바로 실험해볼 수 있으므로
오픈소스를 참고해서 회사 스타일에 맞게 정리하는 게 빠르다.
물론 라이센스 확인은 필수다.

또한, 논문에 기재되지 않은 디자인 초이스나 휴리스틱이 존재할 수 있으므로
원작자의 코드 존재 여부를 우선 파악하는 것이 맞는 것 같다.

만약 이해되지 않는 디자인이나,
기재되지 않은 하이퍼 파라미터 정보가 있다면 레딧에 물어보는 것도 괜찮은거 같다.

(생각보다 관심을 많이 받아서 신기했음)

(생각보다 관심을 많이 받아서 신기했음)

실험, 문제점, 해결책

이렇게 구현이 끝난 모델은 사전에 선정한 데이터셋으로 학습해 보고,
여러 지표를 통해 모델을 평가했다.

문제는 생성된 음성은 원본과 1대1로 비교하는 것이 무의미하다.
음의 높낮이가 다르더라도 충분히 자연스러울 수 있고,
파형이 다르더라도 같은 발음 성분을 가지고 있을 수 있다.

이러다 보니 1대1로 비교하는 것은 무의미하고,
길이가 다른 시퀀스의 유사도나, ASR 모델을 활용하여
의미 있는 평가를 자동화하기 위한 여러 추가 연구도 진행했었다.

그렇게 모델을 평가하고 나면 문제점이 나타난다.
이 모델은 발음을 못 한다, 이 모델은 음질이 안 좋다. 등등
그럼 기존까지 관찰된 여러 모델의 현상을 통해 단점을 커버하기 위한 추가 실험을 진행한다.

아직은 딥러닝이라는 분야가
특정된 데이터셋과 컴퍼넌트의 상호작용을 연역적으로 분석하기 어려우므로
현상과 실험적인 접근이 최선인 것 같다는 생각이 든다.

그래서 조금 답답하기도 하다.

연구하는 방법을 배운 기분

위에 엄청 대단한 거 마냥 글을 썼지만,
사실 당연한 연구 루틴이었을지도 모른다.

앞서 이야기했듯, 나는 그냥 딥러닝에 관심이 있던 학부생이었고,
연구라는 것을 해보거나 배워본 적이 없기 때문에
1년 동안은 정말 벽에 부딪치며 연구하는 방법을 배운 거 같다.

실제로 처음에는 그냥 성능을 높이는 게 목적이니
닥치는 대로 논문을 읽고, 시간 되는 대로 구현하고,
뭐가 안되면 모델 잘못이네, 수정은 하이퍼 파라미터 튜닝하는 정도였다.

그러다 이제 타임라인이 현재에 도달해서,
과거 논문을 구현하는 것이 아니면 추가로 볼 논문이나 모델이 없는 시점에 왔다.
이제는 모델 탓만 할 것이 아닌,
어떤 문제가 있고, 어떻게 해결해야 할지에 대한 고민을 해야 하는 상황이다.

올해 말이 되어서야 나는 가설이라는 걸 세워보고,
실험을 통해 증명하고, 개선하는 일련의 프로세스를 확립해서 업무에 적용해 보고 있다.

그 과정에서 평가 지표도 자동화했고,
컴퍼넌트랑 지표의 상관성, 현상과의 연관성도 하나씩 알아보고 있다.

결과를 보는 그 과정이 재미로 다가왔다.
이제야 길이 환해진 느낌이 든다.

 

엔지니어링 업무

입사해서 연구 관련 업무만을 볼 수는 없었다.

딥러닝 프레임워크

딥러닝 모델 개발은 생각보다 여러 가지 코드를 수반한다.

단순 텍스트 파일과 wav 파일로 이뤄진 데이터를
전처리하여 저장하기 위한 코드도 필요하고,

저장된 데이터를 불러와 모델에 전달,
loss를 계산하여 업데이트하는 학습 과정,

학습이 얼마나 잘 되어 가고 있는지,
모델이 어느 정도 수준을 성취했는지 기록하는 과정,

마지막으로 모델을 통해 서비스를 작동시킬 수 있도록
캡슐화시키고 배포하는 과정 등이 있다.

나는 개발, 학습, 기록, 캡슐화 정도의 과정을 맡았고,
전처리, 후처리, 서비스 개발 및 배포 과정은 다른 분이 맡았다.

이 중에서 배포 과정을 뺀 모델 개발 전 과정을
하나의 구조로 엮은 프레임워크가 있었는데,

코딩 컨벤션이나 문서화도 따로 되어 있지 않았고,
깃은 단순 코드 보관용, 모델 캡슐화는
코드 전체와 체크포인트를 복붙하여 압축파일(?)로 저장하는 구조로 되어 있었다.

9월에 입사한 나는 회사 규모가 커졌을 때,
이 모든 코드가 레거시로 남을게 뻔히 보여 더 커지기 전에 갈아엎을 것을 제안했고,
팀장님도 이에 수긍하셔 11월까지 3개월간의 대규모 리팩토링에 들어간다.

협업 약속

가장 먼저 제안한 것은 브랜치, PR, 코드 리뷰의 3가지 과정이다.

기존까지 모든 코드는 마스터로 들어가고 있었고,
혼자 개발하고 계셨기 때문에 브랜치나 PR의 개념이 없었다.

이젠 둘이 개발해야 하는 상황이고,
마스터는 항상 stable한 상태로 두어야 한다는 전제를 깔았다.
그러고 나니 브랜치, PR, 코드 리뷰는 당연히 쫓아오는 과정이었다.

또 하나는 문서화를 제안했다.
당장 리팩토링을 하려고 보니, 음성 합성 지식이 전무했던 나에겐
주석 하나 없는 코드가 어떤 역할을 하는지 몰랐다.

당시에는 회사에 오래 있을지 모르는 상황이었기에,
다음 사람이 오면 똑같은 일이 일어날 것이고,
이제는 코딩 컨벤션과 주석을 의무적으로 달아야 한다고 전달했다.

의견은 모두 받아들여졌고,
가장 근간이 되는 토대를 확립하고 나서야 리팩토링을 진행할 수 있었다.

리팩토링

가장 먼저 한 것은 각 과정에 맞게 레이어를 분리한 것이다.

모델 클래스를 추상화하고,
이를 토대로 학습, 기록, 추론 과정을 어플리케이션 레이어로 분리했다.

모델을 개발하면 더 이상 복사 붙여넣기 없이,
hyper-parameter와 체크포인트만 가지고 모델을 작동시킬 수 있게 되었다.

딥러닝 컴퍼넌트 중 중복된 코드는 분리하고,
어텐션 모듈같이 추상화가 요구되는 것들은 레이어를 하나 더 두었다.

문제는 전처리 코드였는데,
이 부분은 음성 합성 지식이 전무한 나에겐 함부로 건드릴 수 없는 부분이었다.
결국 전처리 레이어를 두고, 어플리케이션에서 사용 가능하게 두되,
내용물은 레거시 코드에서 가져온 것을 래핑 해둔 정도로 놔둬야 했다.

쓰다 보니 정말 당연한 리팩토링 수순을 밟았지만,
그렇게 만들어진 당시의 프레임워크는 정말 간결했고, 개발자 친화적이었다.

돌고 돌아

하지만 리팩토링된 프레임워크는 1년이 된 시점에서 망가지기 시작했다.

회사는 바빴고,
코드 퀄리티가 떨어지더라도 당장에 작동하는 듯 보이는 코드가 필요했다.

처음과 달리 코드 리뷰는 약식화 되어 갔고,
코드에 구멍이 나기 시작했다.
점점 핫픽스 브랜치가 늘어났고,
원인과 여파의 분석 없이 당장 필요한 부분만 작동하면 머지를 해야 했다.

한 1년 즈음 까지는 나라도 붙잡고,
나라도 리뷰 열심히 해야지 하고 이런저런 리뷰를 남겼지만,
어느 순간부터 나도 포기했다.

회사는 결국은 돈을 벌어와야 했고,
회사가 요구하는 연구 방향이나 개발 방향이 설정된다.
급하다는 이유로 언제 터질지 모르는 구멍을 보고도 모른 척 하게 된 것이다.

이제는 프레임워크가 거의 제구실을 하지 못한다.

작동하는 모델은 상용화된 것 몇 개 뿐이고,
연구용으로 남긴 모델은 작동하지도 않는다.
중간에 experimental feature라고 분리도 해봤지만,
이젠 마스터 브랜치의 모델도 돌아가는 것은 몇 개 없다.

그 외에 학계의 움직임에도 영향을 받았다.

이젠 기성 신호처리 기법들이 딥러닝과 연계되어 들어왔고,
프레임워크는 그에 필요한 새로운 피쳐나 출력물들까지 모두 처리할 수 없었다.

입출력 파이프라인은 난잡해졌고,
결국 안 좋은 의미의 레거시로의 길을 걷고 있는 것 같았다.

앞으로

올 11월 즈음, 나는 더 이상 가면 되돌릴 수 없다는 걸 깨달았다.

하지만 새로운 서비스를 준비하고 있던 팀에는 그걸 모두 뜯어고칠 시간이 없었다.
매일 새로운 실험을 통해 모델을 개선해야 했고,
모델 변형, 실험, 정리, 새 실험 기획만 하다가도 날이 저물었다.

결국 이번 모델 개발이 끝나면,
기간을 잡고 리팩토링을 하거나 새로운 프레임워크를 구성하자는 이야기를 꺼냈다.

이다음에 프레임워크를 재구성하게 된다면,
그때는 지금과 다를 수 있을지 의문이 들기는 한다.

 

CUDA까지 내려갔다 오면

음성은 보통 초당 16k, 22k, 44k 정도의 프레임을 샘플링 하는데,
19년 말, 20년 초까지만 해도 이 프레임 수에 합성 시간이 비례했다.

음성 길이가 길어지면 합성 시간도 길어졌고,
1초의 음성을 만드는데 대략 10초 정도 걸렸던 거 같다.

당연히 실시간 서비스에 사용하기는 어려웠고,
프레임 수에 합성 시간이 비례하지 않는 모델을 찾아봤지만,
음질이 좋지 않아 결국 돌아오게 되었다.

모델 가속화

올해 초 모델을 가속화 하라는 특명이 떨어졌다.
대략 1초 합성에 1초 언저리 즈음 걸리도록

여러 가지 찾아봤다.
TensorRT, TorchScript JIT, ONNX 등등

하지만 공통적인 문제가 있었다.

기본적으로 당시 음성 합성 기술은 대부분이
하나의 프레임을 만들고, 해당 프레임을 토대로 새로운 프레임을 만드는
자기 회귀(autoregressive) 방식으로 작동하고 있었고,
언제 회귀를 끝낼지는 동적으로 판단하기 때문에

  1. iteration 수가 정해져 있어야 하고,
  2. 자기 회귀 없이 forwarding만을 지원하는

가속화 도구 대부분을 사용할 수 없었다.

모델을 분리하여, 병목이라도 가속화 해보려 했지만,
극적인 성능 향상 없이 대부분 1.7배, 2배 정도만 빨라졌다.

목표는 10배이고, 상용 프레임워크는 불가능했기에,
결국 눈을 질끈 감고 CUDA 프레임워크를 개발하기로 했다.

결국 CUDA

팀 내에서 CUDA를 다룰 줄 아는 사람은 없었기에,
급하게 CUDA 서적을 구매해 예시 코드 몇개 적어보고 가속화 작업에 착수했다.

결론부터 이야기하면, 대략 2주 만에 7배 빠른
추론 가속화용 CUDA C++ 프레임워크를 개발해냈다.

내 손으로 텐서플로를 만들어 본다는 기분으로 시작했다.

CUDA 메모리를 추상화하고,
shape과 blob으로 구성된 텐서 구조체를 설정하고,
텐서에서 작동하는 여러 연산을 CUDA 커널로 구현했다.

이후 문제의 모델을 C++에서 재구현하고,
파이토치 체크포인트에서 weight을 불러올 수 있는 기능을 추가했다.

그렇게 만든 CUDA 프레임워크는 처음에 PyTorch보다 느렸다.
당시 야근을 하고 있었는데, 팀장님이랑 같이 한숨부터 쉰 기억이 있다.

이제 하나씩 최적화를 시작했다.
그 중 몇 가지만을 소개하려 한다.

가장 먼저 한 것은 TensorView를 만든 것이었다.

reshape, transpose나 slice의 경우에는 값의 수정 없이 tensor를 보는 관점만 바뀐 것인데,
연산 과정에 매번 값을 복사하여 새로운 Tensor를 만드는 것은 비효율적이다.
따라서 같은 메모리를 공유하되,
값은 수정하지 못하고, stride나 shape만을 수정 가능한 view를 두는 것이 좋다.

두 번째는 메모리 매니저를 따로 둔 것이다.

자기 회귀(AR) 모델처럼 연산이 가볍지만, 반복이 많은 경우에는,
프로파일링을 해보면 연산보단 메모리의 할당과 해제에 병목이 걸리는 경우가 많았다.

당시에 힙 메모리와 malloc 관련 공부를 하고 있었고, 메모리 관리 기법에서 영감을 얻었다.

CUDA 에서 할당받은 메모리는 사용 직후 바로 반환하기보다는, 크기별로 분류해 두었다가,
메모리 요청이 왔을 때 적절한 메모리가 있으면 CUDA를 안 거치고 반환,
아니면 CUDA 에서 추가 할당받는 식으로 매니저를 구성해서 시간을 많이 줄일 수 있었다.

그렇게 해서 총 7배 정도가 빨라졌고, 대략 1.5초에 1초 음성 정도를 만들었다.

이후에 pybind11 활용해서 파이썬으로도 매핑하고,
C++ 개발자 아니어도 쓸 수 있게 구성을 해놨지만

여러 이유로 프로젝트는 폐기되고,
죽은 기술이 되었다.

회사에 묶여서 공개하지 못하는 게 아쉽기도 하다.

CUDA 개발 정리하면서

CUDA 개발하면서 그래도 느낀 점이 좀 있었다.

왜 torch에는 view와 reshape이 따로 있을지, contiguous가 필요한 이유가 뭘지,
왜 채널 크기는 32의 배수로 설정되는 것인지 등등
실상은 많은 디자인이 GPU와 CUDA 에 의해 결정되고 있었다.

딥러닝 하는 분들한테 추천해볼 법한 프로젝트였던 것 같고,
죽은 기술로만 두는 것이 아쉬워
이번 겨울에 개인적으로 러스트와 rustacuda를 통해 재구현해 볼 생각이다.

 

정리

다사다난한 한 해였다.
TTS, 보코더 연구부터 프레임워크, CUDA 개발까지 여러 가지 해본 것도 많았다.

회사 인원도 거진 20명에 가까워지고 있고,
팀과 역할도 더욱 세분되어 동아리처럼 시작했던 것이 더욱 회사 같아졌다.

내년에 목표가 있다면, 지금 정리 중인 모델을 마무리 짓고
개인적으로 논문도 한 편 써 보고 싶다.

한 해를 잘 정리한 거 같아 후회는 없다.
내년에도 딱 이만큼만 했으면 좋겠다.