Ray Objects

Ray Objects

Ray에서 태스크와 액터는 객체(object)를 만들고 계산해요. 이 객체들을 **원격 객체(remote object)**라고 부르는데, 클러스터 어디에든 저장될 수 있기 때문이에요. 객체를 참조할 때는 **객체 참조(object ref)**를 사용해요. 원격 객체는 클러스터의 분산 공유 메모리 객체 스토어에 캐시되고, 클러스터의 각 노드에 객체 스토어가 하나씩 있어요.

출처: Ray Objects

객체 참조

객체 참조는 본질적으로 원격 객체의 값을 보지 않고도 참조하는 데 쓰는 포인터 또는 고유 ID예요. future에 익숙하다면 Ray 객체 참조도 개념적으로 비슷해요.

객체 참조는 두 가지 방법으로 만들 수 있어요.

  1. 원격 함수 호출에서 반환
  2. ray.put()으로 반환
import ray

# Put an object in Ray's object store.
y = 1
object_ref = ray.put(y)

:::note 원격 객체는 불변(immutable)이에요. 즉 생성 후 값이 바뀌지 않아요. 그래서 원격 객체를 여러 객체 스토어에 복사본을 동기화하지 않고도 복제할 수 있어요. :::

객체 데이터 가져오기

객체 참조에서 원격 객체의 결과를 가져올 때 ray.get()을 사용해요. 현재 노드의 객체 스토어에 객체가 없으면 내려받아요.

객체가 numpy 배열이거나 numpy 배열의 모음이라면 get 호출은 제로 카피로 공유 객체 스토어 메모리가 백업하는 배열을 반환해요. 그 외에는 객체 데이터를 파이썬 객체로 역직렬화해요.

import ray
import time

# Get the value of one object ref.
obj_ref = ray.put(1)
assert ray.get(obj_ref) == 1

# Get the values of multiple object refs in parallel.
assert ray.get([ray.put(i) for i in range(3)]) == [0, 1, 2]

# You can also set a timeout to return early from a ``get``
# that's blocking for too long.
from ray.exceptions import GetTimeoutError
# ``GetTimeoutError`` is a subclass of ``TimeoutError``.
@ray.remote
def long_running_function():
    time.sleep(8)

obj_ref = long_running_function.remote()
try:
    ray.get(obj_ref, timeout=4)
except GetTimeoutError:
    # You can capture the standard "TimeoutError" instead
    print("`get` timed out.")
# `get` timed out.

객체 인자 전달

Ray 객체 참조는 Ray 애플리케이션에서 자유롭게 전달할 수 있어요. 태스크·액터 메서드의 인자로, 심지어 다른 객체에 저장해서도 전달할 수 있어요. 객체는 분산 참조 카운팅으로 추적되며, 객체에 대한 모든 참조가 삭제되면 데이터가 자동으로 해제돼요.

객체를 태스크·메서드에 전달하는 방식은 두 가지가 있는데, 전달 방식에 따라 Ray가 태스크 실행 전에 객체를 *역참조(de-reference)*할지 결정해요.

최상위 인자로 객체 전달: 객체를 태스크의 최상위 인자로 직접 전달하면 Ray가 객체를 역참조해요. 즉 모든 최상위 객체 참조 인자의 기본 데이터를 가져오고, 객체 데이터가 완전히 사용 가능해질 때까지 태스크를 실행하지 않아요.

import ray

@ray.remote
def echo(a: int, b: int, c: int):
    """This function prints its input values to stdout."""
    print(a, b, c)

# Passing the literal values (1, 2, 3) to `echo`.
echo.remote(1, 2, 3)
# -> prints "1 2 3"

# Put the values (1, 2, 3) into Ray's object store.
a, b, c = ray.put(1), ray.put(2), ray.put(3)
# Passing an object as a top-level argument to `echo`. Ray will de-reference top-level
# arguments, so `echo` will see the literal values (1, 2, 3) in this case as well.
echo.remote(a, b, c)
# -> prints "1 2 3"

중첩 인자로 객체 전달: 객체를 중첩 객체(예: 파이썬 리스트) 안에 넣어 전달하면 Ray가 역참조하지 않아요. 즉 태스크가 실제 값을 가져오려면 ray.get()을 직접 호출해야 해요. 태스크가 ray.get()을 아예 호출하지 않으면 객체 값이 태스크가 실행되는 머신으로 전송될 필요가 없어요. 가능하면 객체를 최상위 인자로 전달하는 걸 권장하지만, 데이터를 볼 필요 없이 객체를 다른 태스크로 넘겨야 할 때는 중첩 인자가 유용해요.

import ray

@ray.remote
def echo_and_get(x_list):  # List[ObjectRef]
    """This function prints its input values to stdout."""
    print("args:", x_list)
    print("values:", ray.get(x_list))

# Put the values (1, 2, 3) into Ray's object store.
a, b, c = ray.put(1), ray.put(2), ray.put(3)
# Passing an object as a nested argument to `echo_and_get`. Ray does not
# de-reference nested args, so `echo_and_get` sees the references.
echo_and_get.remote([a, b, c])
# -> prints args: [ObjectRef(...), ObjectRef(...), ObjectRef(...)]
#           values: [1, 2, 3]

최상위 vs 중첩 전달 규약은 액터 생성자와 액터 메서드 호출에도 동일하게 적용돼요.

객체의 클로저 캡처

*클로저 캡처(closure-capture)*로도 객체를 태스크에 전달할 수 있어요. 많은 태스크·액터가 그대로 공유하고 싶은 큰 객체가 있고 인자로 반복 전달하고 싶지 않을 때 편리해요. 다만 객체 참조를 클로저로 캡처하는 태스크를 정의하면 참조 카운팅으로 객체가 고정되어, 잡이 끝날 때까지 객체가 제거되지 않는다는 점을 알아야 해요.

import ray

# Put the values (1, 2, 3) into Ray's object store.
a, b, c = ray.put(1), ray.put(2), ray.put(3)

@ray.remote
def print_via_capture():
    """This function prints the values of (a, b, c) to stdout."""
    print(ray.get([a, b, c]))

# Passing object references via closure-capture. Inside the `print_via_capture`
# function, the global object refs (a, b, c) can be retrieved and printed.
print_via_capture.remote()
# -> prints [1, 2, 3]

중첩 객체

Ray는 중첩 객체 참조도 지원해요. 이를 통해 더 하위 객체에 대한 참조를 담은 복합 객체를 만들 수 있어요. Ray는 참조 카운팅으로 모든 바깥 객체 참조가 삭제될 때까지 내부 객체를 살려 둬요.

정리

  • 객체 참조는 ray.put()이나 원격 함수 호출로 만들어요.
  • ray.get()으로 값을 가져오고, numpy 배열은 제로 카피예요.
  • 객체는 불변이고 참조 카운팅으로 자동 해제돼요.
  • 최상위 인자는 역참조, 중첩 인자는 역참조하지 않아요.

더 알아보기