Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

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()