| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- """Column-split of chosen ReplicatedLinear layers over the TP group, bit-identical or not at all.
- DSV41_REPLICATED_SPLIT=wqkv_a (comma list of prefix suffixes). A ReplicatedLinear makes every TP
- rank stream the whole weight for the same output. Here each rank runs the SAME quantized linear on
- its slice of 128-row weight tiles (tiles spread as evenly as possible, e.g. 4/4/3/3 of 14) and the
- columns are all-gathered (padded to the widest slice; RoCE at small M, a byte copy).
- Exactness is checked, not assumed: at the first eager call with M <= DSV41_REPLICATED_SPLIT_MAX_M,
- each rank runs the stock layer and its slice on the same random inputs and compares the columns bit
- for bit; the ranks agree over the TP group (MIN) and either all use the split for that layer or
- all keep the stock path. Graph capture only ever sees the decided path.
- """
- import copy
- import os
- import torch
- SPEC = [s.strip() for s in os.environ.get("DSV41_REPLICATED_SPLIT", "").split(",") if s.strip()]
- MAX_M = int(os.environ.get("DSV41_REPLICATED_SPLIT_MAX_M", "96"))
- TILE = 128
- _LOG = {"built": 0, "on": 0}
- def _scale_params(layer):
- n, k = layer.weight.shape
- out = []
- for name, p in layer.named_parameters(recurse=False):
- if name == "weight" or p is None or p.dim() < 1:
- continue
- if p.dim() >= 2 and p.shape[0] == n:
- out.append((name, "rows"))
- elif p.dim() == 1 and p.numel() == n * (k // 32):
- out.append((name, "flat")) # 128x4-swizzled: 128-row tiles are contiguous
- return out
- def _ranges(n, world):
- tiles = n // TILE
- base, extra = divmod(tiles, world)
- counts = [base + (1 if r < extra else 0) for r in range(world)]
- starts = [sum(counts[:r]) * TILE for r in range(world)]
- return [(s, s + c * TILE) for s, c in zip(starts, counts)], max(counts) * TILE
- def _proxy(layer, n0, n1):
- proxy = copy.copy(layer)
- proxy._parameters = dict(layer._parameters)
- proxy._buffers = dict(layer._buffers)
- proxy._modules = dict(layer._modules)
- k = layer.weight.shape[1]
- proxy._parameters["weight"] = torch.nn.Parameter(layer.weight.data[n0:n1].contiguous(), requires_grad=False)
- for name, kind in _scale_params(layer):
- p = layer._parameters[name]
- sl = p.data[n0:n1] if kind == "rows" else p.data[n0 * (k // 32):n1 * (k // 32)]
- proxy._parameters[name] = torch.nn.Parameter(sl.contiguous(), requires_grad=False)
- for attr in ("output_size", "output_size_per_partition"):
- if hasattr(layer, attr):
- setattr(proxy, attr, n1 - n0)
- proxy._dsv41_split = None
- return proxy
- def _rows(x):
- """M of a plain activation or of a pre-quantized Mxfp8SwizzledInput (data, scales)."""
- if isinstance(x, torch.Tensor):
- return x.shape[0] if x.dim() == 2 else -1
- data = getattr(x, "data", None)
- return data.shape[0] if isinstance(data, torch.Tensor) and data.dim() == 2 else -1
- def _build(layer, orig_forward, group, x_real):
- world, rank = group.world_size, group.rank_in_group
- n, k = layer.weight.shape
- ok = n % TILE == 0 and n // TILE >= world and layer.weight.dtype == torch.float8_e4m3fn
- state = None
- if ok:
- ranges, width = _ranges(n, world)
- n0, n1 = ranges[rank]
- try:
- proxy = _proxy(layer, n0, n1)
- dev = layer.weight.device
- g = torch.Generator(device=dev).manual_seed(4321)
- inputs = [x_real] + [torch.randn((rows, k), generator=g, device=dev, dtype=torch.bfloat16)
- for rows in (1, 6, 16)]
- for x in inputs:
- full = orig_forward(layer, x)[0]
- part = orig_forward(proxy, x)[0]
- ok = ok and bool(torch.equal(full[:, n0:n1], part))
- state = (proxy, ranges, width)
- except Exception as exc:
- print(f"[replicated_split] {layer._dsv41_prefix}: slice failed on rank {rank}: {exc!r}", flush=True)
- ok = False
- flag = torch.tensor([1 if ok else 0], dtype=torch.int32, device=layer.weight.device)
- torch.distributed.all_reduce(flag, op=torch.distributed.ReduceOp.MIN, group=group.device_group)
- agreed = bool(flag.item())
- _LOG["built"] += 1
- _LOG["on"] += int(agreed)
- if rank == 0 and (_LOG["built"] <= 2 or not agreed):
- print(f"[replicated_split] {layer._dsv41_prefix} {n}x{k}: split {'ON' if agreed else 'OFF'} "
- f"(rank 0 bit-exact {ok}; {_LOG['on']}/{_LOG['built']} layers on so far)", flush=True)
- return state if agreed else False
- def install(linear_module):
- """sglang.srt.layers.linear: ReplicatedLinear, per-instance by prefix."""
- if not SPEC:
- return
- cls = linear_module.ReplicatedLinear
- orig_init, orig_forward = cls.__init__, cls.forward
- def __init__(self, *a, **kw):
- orig_init(self, *a, **kw)
- prefix = kw.get("prefix", "") or ""
- self._dsv41_prefix = prefix
- self._dsv41_split = None if any(prefix.endswith(s) for s in SPEC) else False
- def forward(self, x, *a, **kw):
- st = getattr(self, "_dsv41_split", False)
- m = _rows(x) if st is not False else -1
- if st is False or a or kw or m <= 0 or m > MAX_M:
- return orig_forward(self, x, *a, **kw)
- from sglang.srt.distributed import get_tp_group
- group = get_tp_group()
- if group.world_size == 1:
- return orig_forward(self, x)
- if st is None:
- if torch.cuda.is_current_stream_capturing():
- return orig_forward(self, x)
- st = self._dsv41_split = _build(self, orig_forward, group, x)
- if st is False:
- return orig_forward(self, x)
- proxy, ranges, width = st
- local = orig_forward(proxy, x)[0]
- if local.shape[1] < width:
- local = torch.nn.functional.pad(local, (0, width - local.shape[1]))
- gathered = group.all_gather(local.contiguous(), dim=-1) # [M, world * width]
- if all(b - a == width for a, b in ranges):
- return gathered, None
- parts = [gathered[:, r * width:r * width + (b - a)] for r, (a, b) in enumerate(ranges)]
- return torch.cat(parts, dim=-1), None
- cls.__init__ = __init__
- cls.forward = forward
- print(f"[replicated_split] armed for {SPEC} (rows <= {MAX_M})", flush=True)
|