[diffusion] fix: recover ipc jit initialization after interrupted builds (#39034)

Co-authored-by: Mick Qian <mickqian@users.noreply.github.com>
This commit is contained in:
Mick
2026-09-11 19:34:09 +08:00
committed by GitHub
co-authored by Mick Qian
parent 165d8dd177
commit 358c163250
4 changed files with 225 additions and 71 deletions
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.cuh>
#include <cstdint>
namespace sglang::ipc_a2a {
__device__ __forceinline__ unsigned long long now_ns() {
unsigned long long t;
asm volatile("mov.u64 %0, %%globaltimer;" : "=l"(t));
return t;
}
__global__ void spin_wait_kernel(
volatile int* flag, const int* target, int* timed_out, int* peer_timed_out, unsigned long long budget_ns) {
int t = *target;
unsigned long long start = now_ns();
while (*flag < t) {
if (now_ns() - start > budget_ns) {
// both ranks must retire the transport at the same request boundary
*timed_out = 1;
*peer_timed_out = 1;
__threadfence_system();
return;
}
}
__threadfence_system();
}
__global__ void bump_signal_kernel(int* seq, volatile int* peer_flag) {
int v = *seq + 1;
*seq = v;
__threadfence_system();
*peer_flag = v;
}
inline void spin_wait(
tvm::ffi::TensorView flag,
tvm::ffi::TensorView target,
tvm::ffi::TensorView timed_out,
tvm::ffi::TensorView peer_timed_out,
int64_t budget_ns) {
using namespace host;
auto device = SymbolicDevice{};
device.set_options<kDLCUDA>();
TensorMatcher({1}).with_dtype<int32_t>().with_device(device).verify(flag).verify(target).verify(timed_out).verify(
peer_timed_out);
LaunchKernel(1, 1, device.unwrap())(
spin_wait_kernel,
static_cast<int*>(flag.data_ptr()),
static_cast<const int*>(target.data_ptr()),
static_cast<int*>(timed_out.data_ptr()),
static_cast<int*>(peer_timed_out.data_ptr()),
static_cast<unsigned long long>(budget_ns));
}
inline void bump_signal(tvm::ffi::TensorView seq, tvm::ffi::TensorView peer_flag) {
using namespace host;
auto device = SymbolicDevice{};
device.set_options<kDLCUDA>();
TensorMatcher({1}).with_dtype<int32_t>().with_device(device).verify(seq).verify(peer_flag);
LaunchKernel(1, 1, device.unwrap())(
bump_signal_kernel, static_cast<int*>(seq.data_ptr()), static_cast<int*>(peer_flag.data_ptr()));
}
} // namespace sglang::ipc_a2a
@@ -0,0 +1,17 @@
"""CUDA-IPC sequence-counter kernels for two-rank Ulysses."""
from sglang.kernels.jit.utils import cache_once, load_jit
@cache_once
def load_ipc_a2a_sync():
# the shared jit loader releases its lock on process death and publishes
# complete builds atomically, unlike load_inline's persistent lock file
return load_jit(
"ipc_a2a_sync",
cuda_files=["distributed/ipc_a2a.cuh"],
cuda_wrappers=[
("spin_wait", "ipc_a2a::spin_wait"),
("bump_signal", "ipc_a2a::bump_signal"),
],
)
@@ -14,72 +14,17 @@ read of that slot.
"""
import logging
import os
import socket
from collections import OrderedDict
import torch
import torch.distributed as dist
from sglang.kernels.ops.communication.ipc_a2a import load_ipc_a2a_sync
from sglang.multimodal_gen import envs
logger = logging.getLogger(__name__)
_SYNC_DECL = (
"void spin_wait(torch::Tensor flag, torch::Tensor target, torch::Tensor timed_out,"
" torch::Tensor peer_timed_out, int64_t budget_ns);\n"
"void bump_signal(torch::Tensor seq, torch::Tensor peer_flag);"
)
_SYNC_SRC = """
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
__device__ __forceinline__ unsigned long long now_ns() {
// %globaltimer is a nanosecond wall clock, so the budget needs no SM-clock
// conversion -- cudaDevAttrClockRate is not dependable across architectures
// (B200 reports 120 MHz, which would shrink the timeout ~16x).
unsigned long long t;
asm volatile("mov.u64 %0, %%globaltimer;" : "=l"(t));
return t;
}
__global__ void spin_wait_kernel(volatile int* flag, const int* target,
int* timed_out, int* peer_timed_out,
unsigned long long budget_ns) {
int t = *target;
unsigned long long start = now_ns();
while (*flag < t) {
if (now_ns() - start > budget_ns) {
// Give up rather than hang the stream forever. The peer never
// published, so this exchange's data is incomplete. Flag it on the
// peer as well as here: both ranks must retire the transport at the
// same request boundary, or the one that switched to NCCL would post
// a collective the other never posts.
*timed_out = 1;
*peer_timed_out = 1;
__threadfence_system();
return;
}
}
__threadfence_system();
}
__global__ void bump_signal_kernel(int* seq, volatile int* peer_flag) {
int v = *seq + 1;
*seq = v;
__threadfence_system();
*peer_flag = v;
}
void spin_wait(torch::Tensor flag, torch::Tensor target, torch::Tensor timed_out,
torch::Tensor peer_timed_out, int64_t budget_ns) {
spin_wait_kernel<<<1, 1, 0, at::cuda::getCurrentCUDAStream()>>>(
(volatile int*)flag.data_ptr<int>(), target.data_ptr<int>(),
timed_out.data_ptr<int>(), peer_timed_out.data_ptr<int>(),
(unsigned long long)budget_ns);
}
void bump_signal(torch::Tensor seq, torch::Tensor peer_flag) {
bump_signal_kernel<<<1, 1, 0, at::cuda::getCurrentCUDAStream()>>>(
seq.data_ptr<int>(), (volatile int*)peer_flag.data_ptr<int>());
}
"""
class _Unsupported(RuntimeError):
"""This topology cannot run the transport -- an expected outcome, not a bug."""
@@ -183,8 +128,6 @@ class IpcA2AState:
def init(self, group):
import ctypes
from torch.utils.cpp_extension import load_inline
self.rank = dist.get_rank(group=group)
self.group = group
dev = torch.cuda.current_device()
@@ -201,19 +144,7 @@ class IpcA2AState:
)
# kernel-level dereference of peer mappings needs explicit peer access
ctypes.CDLL("libcudart.so").cudaDeviceEnablePeerAccess(peer_dev, 0)
build_dir = os.path.join(
envs.SGLANG_DIFFUSION_CACHE_ROOT, f"ipc_a2a_sync_r{dev}"
)
os.makedirs(build_dir, exist_ok=True)
self.ops = load_inline(
name="ipc_a2a_sync",
cpp_sources=_SYNC_DECL,
cuda_sources=_SYNC_SRC,
functions=["spin_wait", "bump_signal"],
extra_cuda_cflags=["-O3"],
build_directory=build_dir,
verbose=False,
)
self.ops = load_ipc_a2a_sync()
self.flag = torch.zeros(1, dtype=torch.int32, device="cuda")
self.my_seq = torch.zeros(1, dtype=torch.int32, device="cuda")
self.timed_out = torch.zeros(1, dtype=torch.int32, device="cuda")