BYEONGJO's RESEARCH BLOG

new blog in tistory



Project

MLDev - Large Video Processing using Kubernetes

Problem

  • 300시간의 비디오 속 모든 인물 비식별화 처리
    • 모든 frame에 대해 처리
  • Multi-Process
    • ZMQ(ZeroMQ) 사용하여 통신
    • GPU 2개가 달린 서버
    • 4 프로세스 한계 (GPU 당 2 프로세스)
    • 20개 영상(총 1시간) 전처리 -> 10시간 반 소모
    • 단순 계산으로 300시간 영상 전처리 -> 약 3000시간(125일) 소모
  • Deadline: 약 2주

Solving

  • Kubernetes 서버 사용
    • 여러개의 GPU 달린 서버
  • Service, Job을 이용하여 Master, Worker 구현
    • Master: 남은 일 체크 및 일 분배
    • Worker: 받은 일 처리 완료 후 일 요청

Master

Flask를 사용하여 일 분배하는 master service를 구현

run.py

@app.route("/start")
def start():
    """
    Params(GET):
    - input_path=데이터 위치
    - log_path=로깅 파일 위치
    Descriptions:
    - jobs = make_job_list(input_path)
        - 주어진 input_path의 존재하는 모든 mp4 파일명 리스트를 생성(jobs)
    """ 

@app.route("/get_job")
def get_job():
    """
    Descriptions:
    - job = jobs.pop()
    - return job
    """ 

@app.route("/fin_job")
def fin_job():
    """
    Params(GET):
    - job=해당 job
    - status=상태 (1 or -1)
    Descriptions:
    - if not status == 1:
        jobs.append(job)
    """
  • worker들은 /get_job 을 통해 처리해야하는 영상 파일이름을 얻을 수 있다.
  • 일이 끝난 worker는 /fin_job 을 통해 보고를 할 수 있다.
    • 일이 마무리가 안된 경우 다시 jobs에 append 시킨다.

Dockerfile

FROM python:3.6
RUN pip install flask
COPY . /app
WORKDIR /app
ENTRYPOINT ["python"]
CMD ["run.py"]

master.yaml

apiVersion: v1
kind: Service
metadata:
  name: blur-master-service
  namespace: blur
spec:
  selector:
    app: blur-master
  ports:
  - name: http
    protocol: TCP
    port: AAAA
    targetPort: BBBB
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blur-master
  namespace: blur
spec:
  selector:
    matchLabels:
      app: blur-master
  replicas: 1
  template:
    metadata:
      labels:
        app: blur-master
    spec:
      containers:
      - name: blur-master
        image: blur-master:latest
        ports:
        - containerPort: BBBB
        volumeMounts:
        - mountPath: /data
          name: videos
      volumes:
      - name: videos
        hostPath:
          path: /videos/path/
  • type: LoadBalancer: LoadBalancer을 이용하여 서비스
  • replicas: 1: replica는 1로 두었다. jobs 리스트를 전역 변수로 사용하고 있기 때문에 독립적인 pod을 추가로 사용하게 되면 일을 중복으로 나눠주게 됨.
  • volumes: 영상이 있는 스토리즈를 hostPath를 통해 마운트.
    • 모든 워커 노드가 스토리지에 접근 할 수 있어야 함.

Worker

일이 없을 때 까지 master에게 일을 요청하는 worker을 구현. 주어진 일이 없으면 종료되어야 하므로 ReplicaSet 이 아닌 Job 을 이용.

run.py

# load DL model
MODEL_WEIGHT = os.getenv('MODEL_WEIGHT', "/MODEL/WEIGHT.pth")
detector = load_model(MODEL_WEIGHT)

# set ip&port of master service
master_ip = os.getenv('MASTER_IP', 'http://127.0.0.1:8080')

def get_job():
    # master_ip + "/get_job" 으로 GET 요청
    # return job

def set_fin_job(job, status):
    # master_ip + '/fin_job?job={job}&status={status}'.format(job=job, status=status) 으로 GET 요청

def inference(job):
    # Blur all faces in video
    # blur 처리된 모든 frame과 기존 영상 속 오디오를 이용하여 새 영상 생성

def main():
    while True:
        job = get_job()
        status = 0
        
        if not job:
            break
        
        try:
            # 새로운 영상 생성 성공시
            inference(job)
            status = 1
        except:
            # 실패시
            status = -1
        
        set_fin_job(job, status)
  • 환경변수를 통해 모델의 가중치 파일(MODEL_WEIGHT)의 위치를 받는다.
  • 환경변수를 통해 master(flask)에게 요청을 보낼 수 있는 url을 받는다.
  • 일 요청 -> 일 처리 -> 일 보고
  • 일이 없으면 종료

Dockerfile

FROM pytorch/pytorch:1.4-cuda10.1-cudnn7-runtime
RUN apt-get update
RUN apt-get install -y libgl1-mesa-dev
RUN apt-get install -y libgtk2.0-dev
RUN apt-get install -y ffmpeg
RUN pip install opencv-python cmake
RUN pip install dlib ujson
COPY . /app
WORKDIR /app
ENTRYPOINT ["python"]
CMD ["run.py"]

worker.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: blur-worker
  namespace: blur
spec:
  parallelism: 10
  template:
    spec:
      containers:
      - name: blur-worker
        image: blur-worker:latest
        env:
        - name: MASTER_IP
          value: "http://blur-master-service:AAAA"
        - name: MODEL_WEIGHT
          value: "/MODEL/WEIGHT.pth"
        - name: OUTPUT
          value: "/OUTPUT/PATH"
        resources:
          limits:
            nvidia.com/gpu: 1
        volumeMounts:
        - mountPath: /data
          name: videos
      restartPolicy: Never
      volumes:
      - name: videos
        hostPath:
          path: /videos/path/
  backoffLimit: 2
  • kind: Job:
    • ReplicaSet의 경우 restartPolicy: Always 만 사용해야함
    • 일이 끝난 worker가 계속 재실행되어서 자원 낭비
    • Job 컨트롤러를 이용 -> restartPolicy: Never
  • parallelism: 몇개의 pod이 병렬적으로 작업을 할 것인지. ReplicaSet의 replicas 와 같은 개념이다.
  • env:: Master_IP, MODEL_WEIGHT, OUTPUT 환경변수를 사용하여 코드 수정 없이 yaml 파일만 수정할 수 있도록
  • nvidia.com/gpu: 1: 모델당 GPU 1개의 메모리면 충분 하기 때문에 limits을 걸어놓음
  • volumes: 영상이 있는 스토리지를 hostPath를 통해 마운트.
    • 모든 워커 노드가 스토리지에 접근 할 수 있어야 함.

실행 결과

master 실행 결과

master

worker 실행 결과

worker

전처리 결과

  • 20개 영상(총 1시간) 전처리 -> 1시간 반 소모

추가

기존 worker.yaml

limits:
  nvidia.com/gpu:1

위와 같이 gpu를 한개로 제한을 해버리면 사용할 수 있는 gpu 개수 만큼만 parallelism를 선택할 수 있다. 따라서 GPU Share Scheduler (GitHub)을 사용하였다.

변경된 worker.yaml

limits:
  aliyun.com/gpu-mem: 3
  • gpu-mem을 설정하여 pod들이 gpu를 공유하여 해당 memory 만큼만 사용.
  • 여기서 1은 1GB.
  • 전처리 시간 1/2 단축.

References