176 lines
6.1 KiB
Python
176 lines
6.1 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
"""Test sharded loader"""
|
|
# pylint: disable=missing-docstring
|
|
|
|
import pathlib
|
|
import tempfile
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import tvm
|
|
import tvm.testing
|
|
from tvm.script import ir as I
|
|
from tvm.script import relax as R
|
|
from tvm.script import tirx as T
|
|
from tvm.testing import env
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(tvm.runtime.disco is None, reason="disco runtime is not available")
|
|
@pytest.mark.skipif(not env.has_nccl(), reason="need nccl")
|
|
@pytest.mark.skipif(not env.has_multi_gpu(), reason="need multiple gpus")
|
|
def test_callback():
|
|
"""Simulate lazy loading of parameters in a callback
|
|
|
|
The output of a lazy parameter loading, which would accept a
|
|
callback to load the parameters.
|
|
"""
|
|
|
|
@I.ir_module(s_tir=True)
|
|
class Module:
|
|
@T.prim_func(private=True, s_tir=True)
|
|
def slice_A(
|
|
A: T.Buffer((4, 4), "int32"),
|
|
rank: T.int64,
|
|
A_sharded: T.Buffer((2, 4), "int32"),
|
|
):
|
|
for i, j in T.grid(2, 4):
|
|
with T.sblock("slice_A"):
|
|
vi, vj = T.axis.remap("SS", [i, j])
|
|
A_sharded[vi, vj] = A[rank * 2 + vi, vj]
|
|
|
|
@T.prim_func(private=True, s_tir=True)
|
|
def slice_B(
|
|
B: T.Buffer((2, 2), "float32"),
|
|
rank: T.int64,
|
|
B_sharded: T.Buffer((2, 1), "float32"),
|
|
):
|
|
for i in range(2):
|
|
with T.sblock("slice_B"):
|
|
vi = T.axis.spatial(2, i)
|
|
B_sharded[vi, 0] = B[vi, rank]
|
|
|
|
@R.function
|
|
def transform_params(
|
|
rank_arg: R.Prim("int64"),
|
|
fget_item: R.Callable([R.Any, R.Prim("int64")], R.Any),
|
|
):
|
|
cls = Module
|
|
|
|
A = fget_item(R.str("A"), R.prim_value(0))
|
|
A = R.match_cast(A, R.Tensor([4, 4], "int32"))
|
|
A = R.call_tir(
|
|
cls.slice_A,
|
|
(A, rank_arg),
|
|
out_ty=R.Tensor([2, 4], "int32"),
|
|
)
|
|
|
|
B = fget_item(R.str("B"), R.prim_value(1))
|
|
B = R.match_cast(B, R.Tensor([2, 2], "float32"))
|
|
B = R.call_tir(
|
|
cls.slice_B,
|
|
(B, rank_arg),
|
|
out_ty=R.Tensor([2, 1], "float32"),
|
|
)
|
|
|
|
return (A, B)
|
|
|
|
pipeline = tvm.ir.transform.Sequential(
|
|
[
|
|
tvm.relax.transform.LegalizeOps(),
|
|
tvm.s_tir.dlight.ApplyDefaultSchedule(tvm.s_tir.dlight.gpu.Fallback()),
|
|
],
|
|
name="pipeline",
|
|
)
|
|
|
|
with tvm.target.Target("cuda"):
|
|
mod = Module
|
|
mod = pipeline(mod)
|
|
built = tvm.compile(mod, "cuda")
|
|
|
|
num_shards = 2
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_dir = pathlib.Path(temp_dir)
|
|
|
|
# TODO(Lunderberg): Update `disco.Session.load_vm_module` to
|
|
# allow a `tvm.runtime.Module` argument. This would avoid the
|
|
# need for a temporary file.
|
|
shlib_path = temp_dir.joinpath("libtemp.so")
|
|
built.export_library(shlib_path)
|
|
|
|
def run_and_check():
|
|
session = tvm.runtime.disco.ProcessSession(num_workers=num_shards)
|
|
try:
|
|
session.import_python_module("tvm.exec.disco_worker")
|
|
session.init_ccl("nccl", *range(num_shards))
|
|
|
|
worker_device = session.get_global_func("runtime.disco.device")()
|
|
worker_id = session.get_global_func("runtime.disco.worker_rank")()
|
|
callback_maker = session.get_global_func("tests.disco.test_callback")
|
|
fget_item = callback_maker(worker_device)
|
|
vm = session.load_vm_module(shlib_path.as_posix())
|
|
transform_params = vm["transform_params"]
|
|
|
|
params = transform_params(worker_id, fget_item)
|
|
|
|
# Worker 0 is the same PID as the controlling scope, so
|
|
# `debug_get_from_remote(0)` returns the Tensor containing
|
|
# the output.
|
|
params_gpu0 = params.debug_get_from_remote(0)
|
|
assert params_gpu0[0].device == tvm.cuda(0)
|
|
assert params_gpu0[1].device == tvm.cuda(0)
|
|
np.testing.assert_array_equal(
|
|
params_gpu0[0].numpy(),
|
|
[
|
|
[0, 1, 2, 3],
|
|
[4, 5, 6, 7],
|
|
],
|
|
)
|
|
np.testing.assert_array_equal(
|
|
params_gpu0[1].numpy(),
|
|
[[0], [2]],
|
|
)
|
|
|
|
# Worker 1 is a different PID altogether, so
|
|
# `debug_get_from_remote(1)` returns a new Tensor within the
|
|
# calling scope's PID.
|
|
params_gpu1 = params.debug_get_from_remote(1)
|
|
assert params_gpu1[0].device == tvm.cpu()
|
|
assert params_gpu1[1].device == tvm.cpu()
|
|
np.testing.assert_array_equal(
|
|
params_gpu1[0].numpy(),
|
|
[
|
|
[8, 9, 10, 11],
|
|
[12, 13, 14, 15],
|
|
],
|
|
)
|
|
np.testing.assert_array_equal(
|
|
params_gpu1[1].numpy(),
|
|
[[1], [3]],
|
|
)
|
|
finally:
|
|
session.shutdown()
|
|
|
|
tvm.testing.run_with_gpu_lock(run_and_check)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tvm.testing.main()
|