diff --git a/b12x/distributed/pcie_oneshot.cu b/b12x/distributed/pcie_oneshot.cu
index 72f19e3..f1a5698 100644
--- a/b12x/distributed/pcie_oneshot.cu
+++ b/b12x/distributed/pcie_oneshot.cu
@@ -336,6 +336,9 @@ class PCIeAllreduce {
       case 2:
         KL(2);
         break;
+      case 3:
+        KL(3);
+        break;
       case 4:
         KL(4);
         break;
@@ -349,7 +352,7 @@ class PCIeAllreduce {
         KL(10);
         break;
       default:
-        throw std::runtime_error("only supports (2,4,6,8,10) gpus, got " + std::to_string(world_size_));
+        throw std::runtime_error("only supports (2,3,4,6,8,10) gpus, got " + std::to_string(world_size_));
     }
 #undef KL
   }
@@ -367,7 +370,9 @@ static fptr_t init_custom_ar(const std::vector<fptr_t>& fake_ipc_ptrs, torch::Te
   int world_size = fake_ipc_ptrs.size();
   if (world_size > pcie_allreduce::kMaxRanks)
     throw std::invalid_argument("world size > " + std::to_string(pcie_allreduce::kMaxRanks) + " is not supported");
-  if (world_size % 2 != 0) throw std::invalid_argument("Odd num gpus is not supported");
+  if (world_size != 2 && world_size != 3 && world_size != 4 && world_size != 6 && world_size != 8 &&
+      world_size != 10)
+    throw std::invalid_argument("only world sizes 2, 3, 4, 6, 8, and 10 are supported");
   if (rank < 0 || rank >= world_size) throw std::invalid_argument("invalid rank");
 
   pcie_allreduce::Signal* ipc_ptrs[pcie_allreduce::kMaxRanks];
diff --git a/b12x/distributed/pcie_oneshot.py b/b12x/distributed/pcie_oneshot.py
index 9dba6b8..5dbec19 100644
--- a/b12x/distributed/pcie_oneshot.py
+++ b/b12x/distributed/pcie_oneshot.py
@@ -21,7 +21,7 @@ from ._cuda_ipc import CudaRTLibrary
 
 logger = logging.getLogger(__name__)
 
-SUPPORTED_WORLD_SIZES = (2, 4, 6, 8, 10)
+SUPPORTED_WORLD_SIZES = (2, 3, 4, 6, 8, 10)
 SUPPORTED_DTYPES = (torch.float16, torch.bfloat16, torch.float32)
 DEFAULT_MAX_SIZE = 8 * 1024 * 1024
 DEFAULT_RANK_DATA_BYTES = 8 * 1024 * 1024
@@ -471,24 +471,17 @@ class PCIeOneshotAllReduce:
         slab = cls._allocate_shared_buffer(
             exchange_group,
             slab_bytes,
-            zero_fill=False,
+            # Initialize signals before peers can open the IPC slab. A late
+            # memset can erase an arrival posted by a faster rank.
+            zero_fill=True,
             ipc=ipc,
         )
-        try:
-            ipc.cudaMemset(slab.local_ptr + signal_offset, 0, signal_bytes)
-            return _ChannelSharedBuffers(
-                owned_buffer=slab,
-                signal_ptrs=tuple(ptr + signal_offset for ptr in slab.peer_ptrs),
-                eager0_ptrs=tuple(ptr + eager0_offset for ptr in slab.peer_ptrs),
-                eager1_ptrs=tuple(ptr + eager1_offset for ptr in slab.peer_ptrs),
-            )
-        except Exception:
-            for ptr in slab.remote_ptrs:
-                with suppress(Exception):
-                    ipc.cudaIpcCloseMemHandle(ptr)
-            with suppress(Exception):
-                ipc.cudaFree(slab.local_ptr)
-            raise
+        return _ChannelSharedBuffers(
+            owned_buffer=slab,
+            signal_ptrs=tuple(ptr + signal_offset for ptr in slab.peer_ptrs),
+            eager0_ptrs=tuple(ptr + eager0_offset for ptr in slab.peer_ptrs),
+            eager1_ptrs=tuple(ptr + eager1_offset for ptr in slab.peer_ptrs),
+        )
 
     @property
     def signal_ptrs(self) -> tuple[int, ...]:
