Practical Tips for Machine Learning Engineers

Practical Tips for Machine Learning Engineers
Photo by Kevin Ku / Unsplash

Linux

  • 리눅스나 운영체제 관련된 많은 것들을 알고 있으면 가끔씩 도움이 됩니다.

    • UFS, /dev/shm의 실제 디바이스, tmpfs, soft/hard link, free출력 결과, stdin/stdout/stderr/pipe 등
  • swap memory

    • 싼 AWS 인스턴스를 돌리거나 기타 등등 다양한 경우에서 램이 부족할 때, 디스크의 일부를 램 대신 쓸 수 있는 swap memory라는 게 있습니다. RAM이 부족해 안 돌아가는 경우를 명령어 몇 줄로 해결할 수 있으므로, 필요한 빈도는 높지는 않아도 필요한 경우는 이것 외에 더 간단한 해결책이 없습니다.
  • .zshrc, PATH가 뭐고 무슨 역할인지 정도는 알아두기

  • 다른 명령어 출력을 텍스트 형태로 다른 명령어에 넣어주는 법

    echo `pwd`
    echo $(pwd)
    
    e.g. ln -s `pwd`/* /new_directory/
    
  • Useful commands

    tail -f (your_log_file)
    # f refers to `follow`. 실시간으로 파일에 찍히는 로그를 관람할 때 유용합니다.
    ls | wc -l
    # counting file
    watch -n1 nvidia-smi
    # nvidia-smi every 1 sec
    
    ps aux # 모든 프로세스를 소유자 정보와 함께 출력. 여러 명이 같이 쓰는 클러스터에서 고부하 작업 돌리는 범인 찾기(?) 혹은 텐서보드 훔쳐보기(?)에 유용함
    ps aux | more # --More--과 함께 나와서 스크롤이 가능하도록 출력
    ps aux | grep something
    ps -ef # PPID(parent PID)와 함께 부모-자식 관계 보는 데 사용
    
    top # htop, gtop 등 UI가 다른 것도 존재함
    
    kill -9 (PID)
    
    (sleep 50; echo done1) & # & : background processing. 50초후 done1 출력하는 걸 background에서 돌림
    
    du -sh * | sort -h # 현재디렉토리 파일/폴더 용량순정렬
    df -lh # human readable하게 저장공간 용량 출력
    free -lh # 현재 메모리 Mem,Low,High,Swap 출력.
    
  • 쉘 스크립트는 코파일럿과 chatgpt에게 맡기기

Python basics

  • list comprehension을 잘 쓰자

  • zip, map, filter 같은건 잘 쓰면 쓸모있긴 한데 좀 가독성이 떨어진다고 느껴지긴 합니다. js랑 달리 list.map(fn)이 아니라 map(fn, list) 문법이라 그런가..

  • native 라이브러리인 typing과 mypy, pydantic 섞어쓰면 타입체킹에 좋습니다. enum도 비슷한 맥락에서 쓸모 있음

  • tempfile: 임시파일/폴더 필요할 때 쓸모있습니다. context manager 형태로 간단하게 쓸 수 있습니다.

  • itertools: product, combination, permutation, chain, batched 등 쓸모있는 게 많습니다. batched는 python 3.12부터 됩니다.

  • functools: partial, cache, cachedproperty, lru_cache 같은 거 갖다 바로 쓰기 좋습니다

    • decorator을 만들 때 __name__, __doc__을 옮겨주는 wraps라는 친구가 있습니다. https://stackoverflow.com/questions/308999/what-does-functools-wraps-do
      • name, doc 옮기는 게 무슨 쓸모인가 생각하실 수도 있는데 본인이 쓴 코드를 본인만 읽을 게 아니라면 읽을 사람이 많아지면 많아질수록 유용할 겁니다.
  • pickle: pickle의 원리와 한계를 미리 알고 있으면 좋습니다

    • 외부 라이브러리의 객체를 pickle했다가 외부 라이브러리 버전/소스코드가 바뀌면 어떻게 되는지 아시나요?
    • c, cpp 관련된 객체가 왜 pickle이 안되는 경우가 있는지 아시나요?
  • contextlib

    • contextlib.contextmanager로 callable을 contextmanager로 만들 수 있고, contextlib.ContextDecorator로 contextmanager을 decorator로 만들 수 있습니다.

    • 예시

      # logging logger
      @contextmanager
      def simple_timed(name: str = "..."):
          torch.cuda.synchronize()
          t0 = time.time()
          yield
          torch.cuda.synchronize()
          logger.info(f"[{name}] done in {time.time() - t0:.3f} s", stacklevel=2)
      
  • logging으로 다른 라이브러리의 minimum logging level을 바꾸는 방법

    • logging.getLogger("filelock").setLevel(logging.INFO)
  • timeit으로 globals() 받는 법

    def f(x):
     return x**2
    def g(x):
     return x**4
    def h(x):
     return x**8
    
    import timeit
    print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))
    
  • deque 같은 자료구조 급하게 필요할 때 collections에서 갖다 쓰면 됩니다

  • decimal, fractions 같은 라이브러리는 다른 때는 쓸 때도 있었는데 MLE로서는 쓸 일이 거의 없는듯..

Python intermediate

기초적인 파이썬 문법 정도는 이미 알고 있다고 가정했을 때, 추가로 알면 좋은 것들은 다음과 같습니다.

Pytorch

Pytorch는 머신러닝/딥러닝 전반에 있어 기초적으로 사용되는 매우 중요한 라이브러리입니다.

머신러닝 엔지니어로서 몰라도 되는 내용도 있으나, 다양한 상황에 유연하게 대응하기 위해서는 pytorch docs를 정독하고 있어두는 게 좋습니다.

데이터 처리

  • (중요)일단 https://github.com/huggingface/datasets을 쓰면 많은 것들이 해결됩니다
    • 특히 중~대규모 데이터일수록 유용
    • parallel/batched map, filter, sharding 등등..
    • pyarrow도 내부적으로 잘 쓰고 있어요
    • 노드 하나에서 처리할 수 없을 정도 규모의 데이터라면 다른 방식이 필요할 것 같긴 합니다
  • multi-node로 데이터셋을 ML 모델을 이용해 GPU를 이용해 처리할 일이 있을 때 https://github.com/huggingface/accelerate도 쓸만합니다
    • 그냥 모델/데이터셋 accelerator.prepare로 감싸버리고 쉘에서 accelerate 실행하면 병렬처리가 됨
  • pandarallel: parallel pandas apply
  • cuDF
    • GPU DataFrame library for loading joining, aggregating, filtering, and otherwise manipulating data
  • 이미지 처리
    • pil vs cv2: 저는 범용적으로 pil, 가끔 성능이 필요한 경우 cv2를 씁니다. pil로 1024x1024 이미지 png encode/decode하면 cv2보다 5배 이상 느려서 한 장에 581ms도 걸리는 걸 본 적이 있어요. 근데 이게 또 jpeg는 둘이 시간이 같아서 참 이상합니다.
    • torchvision.transforms vs albumentations: albumentations는 PIL 대신 cv2 위주로 작업하며, 통상 서너배 이상 빠른 속도에 더 다양한 기능을 갖습니다.

병렬처리

  • native multithread/multiprocessing
  • i/o bound 상황은 multithread 기반, compute bound는 multiprocessing 기반을 쓰되, multiprocess는 프로세스를 만들 때 느리다는 것 등등을 알아둬야 합니다
  • joblib
  • https://github.com/niedakh/pqdm: Comfortable parallel TQDM using concurrent.futures
  • https://github.com/ray-project/ray: 이 친구 병렬처리만 되는 게 아니라 여러가지 기능이 더 있습니다
    • Data: Scalable Datasets for ML
    • Train: Distributed Training
    • Tune: Scalable Hyperparameter Tuning
    • RLlib: Scalable Reinforcement Learning
    • Serve: Scalable and Programmable Serving

ML Experiment Management

  • Tensorboard, Neptune, Wandb 등
    • wandb, neptune 모두를 모른다면 한번 찾아보시길 권장합니다
  • config 관리
    • argparse, dataclass, json/yaml 등 섞어쓰기
    • omegaconf, hydra
    • wandb에 config 저장하도록 하기 등

기타 라이브러리들

DevOps (for MLE)

DevOps 엔지니어나 MLOps 엔지니어면 Ops쪽으로 알아야 할 게 정말 많은데, 여기는 일단 Ops 엔지니어가 아니더라도 알면 좋은 것들을 서술합니다.

  • github workflow는 알고 있을거라 생각합니다
  • pyproject.toml과 연관된 것들
    • https://github.com/python-poetry/poetry: package dependency manager
      • 디펜던시를 관리 안해주다시피하는 pip과는 다르게, 디펜던시가 꼬일 일이 없도록 철저히 관리해주는 디펜던시 매니저입니다. 사용법도 매우 간편합니다.
      • 개인적으로 1.5.0버전에서 1.7.1버전으로 올렸을때 엄청난 속도 향상을 체감했습니다. 원래 240초 걸리던 poetry lock, poetry add 같은 명령어가 30초가 걸립니다.
      • 근데 토치는 어떻게 해야 가장 좋은지 잘 모르겠어요! 토치 외에 다른 라이브러리 디펜던시 관리에는 매우 유용함.
    • https://github.com/nat-n/poethepoet: make in pyproject.toml
    • black, isort, ruff: formatter, linter
      • 특히 ruff: 기존의 formatter, linter의 기능을 대부분 가지고 있으며, 호환도 되고, rust 기반이라 매우 빠릅니다.
  • pytest: for unittest
  • https://github.com/python/mypy: for static type check
    • 구버전 쓰면 서너배 느릴 수 있습니다. github workflow에서 타입체킹만 10분 걸리는 걸 볼 수 있음..
    • 동적 타입체킹을 위한 라이브러리가 또 있다고 들었는데 까먹었습니다
  • https://github.com/pre-commit/pre-commit
  • https://github.com/pydantic/pydantic

개발 환경 관리하기

  • zsh + ohmyzsh 조합: 예전에 nomadcoders에서 리눅스 기초, 블록체인 강의 듣다가 처음 알았던 것 같은데, zsh + ohmyzsh 설치해두고 theme="random" 달아두면 매일매일 새로운 예쁜 터미널을 보며 좋아할 수 있습니다.
  • git, ssh는 당연히 아실텐데, git 유용한 팁 몇가지:
    • 하나 이상의 커밋을 조작할 일이 생기면 git filter-branch를 잘 써보자. (”포크레인”으로 git-scm에서는 비유합니다.)

      • 모든 commit에서 파일 제거

      • https://git-scm.com/book/ko/v2/Git-도구-히스토리-단장하기#_git_amend 의 하단부에 아래 코드가 나옵니다.

        $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
        Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
        Ref 'refs/heads/master' was rewritten
        
      • 모든 commit에서 author name, email 수정 (이런게 존재한다는 사실만 미리 알아두고 나중에 ChatGPT한테 물어보고 복붙하면 될 듯..?)

        $ git filter-branch --commit-filter '
                if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
                then
                        GIT_AUTHOR_NAME="Scott Chacon";
                        GIT_AUTHOR_EMAIL="schacon@example.com";
                        git commit-tree "$@";
                else
                        git commit-tree "$@";
                fi' HEAD
        
      • https://rtyley.github.io/bfg-repo-cleaner/ : git filter-branch를 간략화한 친구입니다

        • Removing Crazy Big Files
        • Removing Passwords, Credentials & other Private data
    • 마지막 커밋에 빠트린 파일이 있을 때 stage한 다음에 amend하면 됩니다. 같은 이치로 직전 커밋 수정하고 싶으면 다 add하고 amend하면 됩니다.

      git add forgotten_file
      git commit --amend
      
    • 배포할 때 git tag를 잘 쓰면 편리합니다

    • git lfs migrate: 이미 여러번 커밋에 올라간 특정 파일을 뒤늦게 git lfs로 관리하고 싶을 때 편리합니다. 해당 파일이 포함된 모든 커밋을 수정해주거든요.

IDE(VSCode)

  • Useful VSCode Extensions

    • python/jupyter, copilot, indent-rainhow, Material Theme, Everforest(제가 쓰는 테마), black/ruff, path intellisense(소스코드에서 경로입력 도와줌)
  • User settings.json 설정해서 파일 저장하면 자동 포매팅되게 하기

    • 개인적으로, vscode-ruff에 formatter args가 생기면 모두 ruff로만 할 예정
      • 없는 이유는 포매팅에 커맨드라인 인자를 아예 다 빼버린다고 디자인을 했었기 때문으로 추정(지금은 line-length만 롤백 상태)
      	"notebook.codeActionsOnSave": {
            // "source.fixAll": true,
            "source.organizeImports": true
        },
        "notebook.formatOnSave.enabled": true,
        "[python]": {
            "editor.codeActionsOnSave": {
                "source.fixAll.ruff": true,
                "source.organizeImports.ruff": true
            },
            // "editor.defaultFormatter": "charliermarsh.ruff",
            "editor.defaultFormatter": "ms-python.black-formatter",
            "editor.formatOnSave": true
        },
        "black-formatter.args": ["--line-length=119"],
        "ruff.lint.args": ["--line-length=119"],
        "python.analysis.autoFormatStrings": true,
        "python.analysis.completeFunctionParens": true,
    

가상환경

anaconda

  • anaconda 대신 miniconda를 씁시다
    • 프로젝트별로 다른 virtual env를 쓰는 게 best practice라고 생각하고, 그럴 경우 base env는 쓸일이 없는데, anaconda는 base env가 무겁습니다
    • base env가 아닌 다른 env에서 conda install을 했을 때도 solving environment를 느려지게 하는 주범도 무거운 base env라고 추측하고 있습니다.
    • 설치파일 용량만 봐도 아나콘다는 1기가 미니콘다는 100메가입니다..
  • mamba, micromamba라는 c로 anaconda를 구현한 친구들도 있지만 사실 pip이 아닌 conda로 패키지 설치할 일이 많지는 않아서 miniconda를 쓰는 것만으로도 필요성을 느낀 적은 없습니다
  • environment 재현하는 작은 꿀팁
    • 하나의 디스크를 공유해서 쓰는 경우처럼, 동료 abc의 가상환경에 직접적으로 접근이 가능한 경우, 3초 들여서 conda create -n myenv —clone /home/abc/miniconda3/envs/myenv 치고 몇초 기다리는게 훨씬 이득일 수도 있습니다.
      • conda env export 같은 명령어 쳤다가, conda 외부의 것들이 얼마나 다르냐에 따라 재현이 안되는 경우도 잦거든요.
    • 또는 심지어 source 명령어를 동료 home에 있는 환경에 대고 쳐도 됩니다

poetry

  • poetry도 가상환경을 가지고 있습니다. 잘 쓰면 프로젝트별로 자동으로 환경이 관리되서 매우 편리합니다
  • 참고) poetry는 가상환경을 만들어도 python executable은 symlink를 걸어 씁니다.
  • 일부 라이브러리를 빠르게 찍먹하거나(특히 주피터 노트북으로 실험하는 경우) 버전을 빠르게 갈아끼우면서 테스트를 해야하는 경우, poetry add/remove를 쓰지 말고 그냥 pip install uninstall을 하는 게 더 속편할 때도 있습니다. 실험은 pip로 하고, 나중에 커밋이나 배포 올릴때만 poetry로 디펜던시 안 꼬이게 정리하면 됩니다.

ChatGPT

  • 정규식, SQL, 쉘 스크립트 짜기, 기타등등을 시키면 잘합니다
  • 사실 코드는 ChatGPT/Copilot이 하고 MLE는 copy&paste만 잘 하면 되는 게 아닐까..?

가끔 인생 살다 필요해서 급하게 쓸만한 ML thing

기타 MLE들이 범용적으로 읽으면 좋을법한 것들