| 1 | #include "torch_utils.h" |
| 2 | |
| 3 | #include <torch/csrc/stable/macros.h> |
| 4 | #include <torch/csrc/stable/accelerator.h> |
| 5 | #include <torch/csrc/stable/tensor.h> |
| 6 | #include <torch/csrc/stable/ops.h> |
| 7 | #include <torch/headeronly/core/ScalarType.h> |
| 8 | #include <torch/csrc/stable/device.h> |
| 9 | |
| 10 | #include "custom_all_reduce.cuh" |
| 11 | |
| 12 | // Fake pointer type, must match fptr_t type in ops.h. |
| 13 | // We use this type alias to indicate when pointers are passed in as int64_t. |
| 14 | using fptr_t = int64_t; |
| 15 | static_assert(sizeof(void*) == sizeof(fptr_t)); |
| 16 | |
| 17 | fptr_t init_custom_ar(const std::vector<fptr_t>& fake_ipc_ptrs, |
| 18 | torch::stable::Tensor& rank_data, int64_t rank, |
| 19 | bool fully_connected) { |
| 20 | int world_size = fake_ipc_ptrs.size(); |
| 21 | if (world_size > vllm::kMaxCustomCollectiveRanks) |
| 22 | throw std::invalid_argument("world size > 16 is not supported"); |
| 23 | if (world_size % 2 != 0) |
| 24 | throw std::invalid_argument("Odd num gpus is not supported for now"); |
| 25 | if (rank < 0 || rank >= world_size) |
| 26 | throw std::invalid_argument("invalid rank passed in"); |
| 27 | |
| 28 | vllm::Signal* ipc_ptrs[vllm::kMaxCustomCollectiveRanks]; |
| 29 | for (int i = 0; i < world_size; i++) { |
| 30 | ipc_ptrs[i] = reinterpret_cast<vllm::Signal*>(fake_ipc_ptrs[i]); |
| 31 | } |
| 32 | return (fptr_t) new vllm::CustomAllreduce( |
| 33 | ipc_ptrs, rank_data.mutable_data_ptr(), rank_data.numel(), rank, |
| 34 | world_size, fully_connected); |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Make sure tensor t's data lies completely within ((char)t.data_ptr()) + |
| 39 | * t.numel() * t.element_size(). This is slightly weaker than t.is_contiguous() |
| 40 | * because it allows transpose of contiguous slice (i.e. slicing the first |
| 41 | * dimension). Currently, we require this because stride information is not |
| 42 | * passed into the kernels and we treat input tensors as flat. |
| 43 | * |
| 44 | * Examples |
| 45 | * A = torch.zeros(3, 3, 3) |
| 46 | * 1. A: OK |
| 47 | * 2. A[1:]: OK |
| 48 | * 3. A.permute(2, 0, 1): OK |
| 49 | * 4. A[1:].permute(2, 0, 1): OK |
| 50 | * 5. A[None].expand(2, -1, -1, -1): Not OK |
| 51 | * 6. A[:, 1:, 1:]: Not OK |
| 52 | */ |
| 53 | bool _is_weak_contiguous(torch::stable::Tensor& t) { |
| 54 | if (t.is_contiguous()) { |
| 55 | return true; |
| 56 | } |
| 57 | int64_t storage_nbytes = 0; |
| 58 | TORCH_ERROR_CODE_CHECK(aoti_torch_get_storage_size(t.get(), &storage_nbytes)); |
| 59 | return storage_nbytes - t.storage_offset() * t.element_size() == |
| 60 | static_cast<int64_t>(t.numel() * t.element_size()); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Performs an out-of-place allreduce and stores result in out. |
| 65 | * |
| 66 | * If _reg_buffer is null, assumes inp.data_ptr() is already IPC-registered. |
| 67 | * Otherwise, _reg_buffer is assumed to be IPC-registered and inp is first |
| 68 | * copied into _reg_buffer. |
| 69 | */ |
| 70 | void all_reduce(fptr_t _fa, torch::stable::Tensor& inp, |
| 71 | torch::stable::Tensor& out, fptr_t _reg_buffer, |
| 72 | int64_t reg_buffer_sz_bytes) { |
| 73 | auto fa = reinterpret_cast<vllm::CustomAllreduce*>(_fa); |
| 74 | const torch::stable::accelerator::DeviceGuard device_guard( |
| 75 | inp.get_device_index()); |
| 76 | const cudaStream_t stream = get_current_cuda_stream(inp.get_device_index()); |
| 77 | |
| 78 | STD_TORCH_CHECK((inp.scalar_type()) == (out.scalar_type())); |
| 79 | STD_TORCH_CHECK((inp.numel()) == (out.numel())); |
| 80 | STD_TORCH_CHECK(_is_weak_contiguous(out)); |
| 81 | STD_TORCH_CHECK(_is_weak_contiguous(inp)); |
| 82 | auto input_size = inp.numel() * inp.element_size(); |
| 83 | auto reg_buffer = reinterpret_cast<void*>(_reg_buffer); |
| 84 | if (reg_buffer) { |
| 85 | STD_TORCH_CHECK((input_size) <= (reg_buffer_sz_bytes)); |
| 86 | STD_CUDA_CHECK(cudaMemcpyAsync(reg_buffer, inp.const_data_ptr(), input_size, |
| 87 | cudaMemcpyDeviceToDevice, stream)); |
| 88 | } else { |
| 89 | reg_buffer = inp.mutable_data_ptr(); |
| 90 | } |
| 91 | switch (out.scalar_type()) { |
| 92 | case torch::headeronly::ScalarType::Float: { |
| 93 | fa->allreduce<float>(stream, reinterpret_cast<float*>(reg_buffer), |
| 94 | reinterpret_cast<float*>(out.mutable_data_ptr()), |
| 95 | out.numel()); |
| 96 | break; |
| 97 | } |
| 98 | case torch::headeronly::ScalarType::Half: { |
| 99 | fa->allreduce<half>(stream, reinterpret_cast<half*>(reg_buffer), |
| 100 | reinterpret_cast<half*>(out.mutable_data_ptr()), |
| 101 | out.numel()); |
| 102 | break; |
| 103 | } |
| 104 | #if (__CUDA_ARCH__ >= 800 || !defined(__CUDA_ARCH__)) |
| 105 | case torch::headeronly::ScalarType::BFloat16: { |
| 106 | fa->allreduce<nv_bfloat16>( |
| 107 | stream, reinterpret_cast<nv_bfloat16*>(reg_buffer), |
| 108 | reinterpret_cast<nv_bfloat16*>(out.mutable_data_ptr()), out.numel()); |
| 109 | break; |
| 110 | } |
| 111 | #endif |
| 112 | default: |
| 113 | throw std::runtime_error( |
| 114 | "custom allreduce only supports float32, float16 and bfloat16"); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | void dispose(fptr_t _fa) { |
| 119 | delete reinterpret_cast<vllm::CustomAllreduce*>(_fa); |
| 120 | } |
| 121 | |
| 122 | int64_t meta_size() { return sizeof(vllm::Signal); } |
| 123 | |
| 124 | void register_buffer(fptr_t _fa, const std::vector<fptr_t>& fake_ipc_ptrs) { |
| 125 | auto fa = reinterpret_cast<vllm::CustomAllreduce*>(_fa); |
| 126 | STD_TORCH_CHECK(fake_ipc_ptrs.size() == fa->world_size_); |
| 127 | void* ipc_ptrs[vllm::kMaxCustomCollectiveRanks]; |
| 128 | for (int i = 0; i < fake_ipc_ptrs.size(); i++) { |
| 129 | ipc_ptrs[i] = reinterpret_cast<void*>(fake_ipc_ptrs[i]); |
| 130 | } |
| 131 | fa->register_buffer(ipc_ptrs); |
| 132 | } |
| 133 | |
| 134 | // Use vector<int64_t> to represent byte data for python binding compatibility. |
| 135 | std::tuple<std::vector<int64_t>, std::vector<int64_t>> |
| 136 | get_graph_buffer_ipc_meta(fptr_t _fa) { |
| 137 | auto fa = reinterpret_cast<vllm::CustomAllreduce*>(_fa); |
| 138 | auto [handle, offsets] = fa->get_graph_buffer_ipc_meta(); |
| 139 | std::vector<int64_t> bytes(handle.begin(), handle.end()); |
| 140 | return std::make_tuple(bytes, offsets); |
| 141 | } |
| 142 | |
| 143 | // Use vector<int64_t> to represent byte data for python binding compatibility. |
| 144 | void register_graph_buffers(fptr_t _fa, |
| 145 | const std::vector<std::vector<int64_t>>& handles, |
| 146 | const std::vector<std::vector<int64_t>>& offsets) { |
| 147 | auto fa = reinterpret_cast<vllm::CustomAllreduce*>(_fa); |
| 148 | std::vector<std::string> bytes; |
| 149 | bytes.reserve(handles.size()); |
| 150 | for (int i = 0; i < handles.size(); i++) { |
| 151 | bytes.emplace_back(handles[i].begin(), handles[i].end()); |
| 152 | } |
| 153 | bytes.reserve(handles.size()); |
| 154 | fa->register_graph_buffers(bytes, offsets); |
| 155 | } |
| 156 | |
| 157 | std::tuple<fptr_t, torch::stable::Tensor> allocate_shared_buffer_and_handle( |
| 158 | int64_t size) { |
| 159 | int device_index; |
| 160 | STD_CUDA_CHECK(cudaGetDevice(&device_index)); |
| 161 | const torch::stable::accelerator::DeviceGuard device_guard(device_index); |
| 162 | void* buffer; |
| 163 | cudaStreamCaptureMode mode = cudaStreamCaptureModeRelaxed; |
| 164 | const cudaStream_t stream = get_current_cuda_stream(device_index); |
| 165 | STD_CUDA_CHECK(cudaThreadExchangeStreamCaptureMode(&mode)); |
| 166 | |
| 167 | // Allocate buffer |
| 168 | #if defined(USE_ROCM) |
| 169 | // data buffers need to be "uncached" for signal on MI200 |
| 170 | STD_CUDA_CHECK( |
| 171 | hipExtMallocWithFlags((void**)&buffer, size, hipDeviceMallocUncached)); |
| 172 | #else |
| 173 | STD_CUDA_CHECK(cudaMalloc((void**)&buffer, size)); |
| 174 | #endif |
| 175 | STD_CUDA_CHECK(cudaMemsetAsync(buffer, 0, size, stream)); |
| 176 | STD_CUDA_CHECK(cudaStreamSynchronize(stream)); |
| 177 | STD_CUDA_CHECK(cudaThreadExchangeStreamCaptureMode(&mode)); |
| 178 | |
| 179 | // Create IPC memhandle for the allocated buffer. |
| 180 | // Will use it in open_mem_handle. |
| 181 | auto handle = torch::stable::empty( |
| 182 | {static_cast<int64_t>(sizeof(cudaIpcMemHandle_t))}, |
| 183 | torch::headeronly::ScalarType::Byte, std::nullopt, |
| 184 | torch::stable::Device(torch::stable::DeviceType::CPU)); |
| 185 | STD_CUDA_CHECK(cudaIpcGetMemHandle( |
| 186 | (cudaIpcMemHandle_t*)handle.mutable_data_ptr(), buffer)); |
| 187 | |
| 188 | return std::make_tuple(reinterpret_cast<fptr_t>(buffer), handle); |
| 189 | } |
| 190 | |
| 191 | fptr_t open_mem_handle(torch::stable::Tensor& mem_handle) { |
| 192 | void* ipc_ptr; |
| 193 | STD_CUDA_CHECK(cudaIpcOpenMemHandle( |
| 194 | (void**)&ipc_ptr, |
| 195 | *((const cudaIpcMemHandle_t*)mem_handle.const_data_ptr()), |
| 196 | cudaIpcMemLazyEnablePeerAccess)); |
| 197 | return reinterpret_cast<fptr_t>(ipc_ptr); |
| 198 | } |
| 199 | |
| 200 | void free_shared_buffer(fptr_t buffer) { |
| 201 | STD_CUDA_CHECK(cudaFree(reinterpret_cast<void*>(buffer))); |
| 202 | } |