@@ -8253,80 +8253,89 @@ def apply(self, paddle_api: str) -> ConvertResult:
82538253
82548254elif op_name == "fused_swiglu_scale_clamp":
82558255 # _run_custom_op("fused_swiglu_scale_clamp", x, scale, max_val)
8256- # fwd: split x -> (x1, x2); out = clamp(silu(x1) * x2 * scale, -max_val, max_val)
8257- # Paddle custom op returns a list; we return [out] to match.
8256+ # clamp 语义对齐 PaddleFleet fusions/fused_swiglu_scale.py::
8257+ # fused_swiglu_scale_forward (gate 只截上限, value 对称截断),
8258+ # 精度顺序对齐 CUDA kernel VectorizedFusedSwiGLUFwd 以做到 bit 级一致:
8259+ # g = min(gate, cv); v = clamp(value, -cv, cv)
8260+ # out = (T)((g * sigmoid(g)) * v * s) # 全程 fp32, 只在最后舍入一次
8261+ # 注意: fleet 的 CPU/XPU fallback 写成
8262+ # (silu(g)*v).cast(x.dtype) * scale.cast(x.dtype)
8263+ # 比 kernel 多两次 bf16 舍入, 与 CUDA 算子本身不 bit 一致, 故这里按 kernel 写。
82588264 x = arg1 # shape [..., 2D]
82598265 scale = arg2 # scalar or tensor [..., 1] or [...,]
82608266 max_val = arg3 # scalar
8267+ _cv = float(max_val)
82618268 _hidden = x.shape[-1] // 2
8262- if x.shape[-1] == 0:
8263- result = [torch.empty((*x.shape[:-1], 0), dtype=x.dtype, device=x.device)]
8264- elif x.shape[-1] % 2 != 0:
8265- raise ValueError(
8266- f"input shape is invalid for input of size {tuple(x.shape)}: "
8267- "fused_swiglu_scale_clamp requires an even last dimension"
8268- )
8269+ _x_fp32 = x.to(torch.float32)
8270+ _gate = torch.clamp(_x_fp32[..., :_hidden], max=_cv)
8271+ _val = torch.clamp(_x_fp32[..., _hidden:], min=-_cv, max=_cv)
8272+ if torch.is_tensor(scale):
8273+ _scale_exp = scale.to(torch.float32)
82698274 else:
8270- _outer = x.numel() // (_hidden * 2)
8271- _x_2d = x.reshape(_outer, _hidden * 2)
8272- _out = torch.empty((_outer, _hidden), dtype=x.dtype, device=x.device)
8273- if torch.is_tensor(scale):
8274- scale = scale.view(*scale.shape[:-1], 1) if scale.dim() > 1 else scale.unsqueeze(-1)
8275- _workspace_bytes = 32 << 30
8276- _bytes_per_row = max(1, _hidden * (4 * 4 + x.element_size()))
8277- _row_chunk = max(1, min(_outer, _workspace_bytes // _bytes_per_row))
8278- with torch.no_grad():
8279- for _row_start in range(0, _outer, _row_chunk):
8280- _row_end = min(_outer, _row_start + _row_chunk)
8281- _lhs = _x_2d[_row_start:_row_end, :_hidden].to(torch.float32)
8282- _rhs = _x_2d[_row_start:_row_end, _hidden:].to(torch.float32)
8283- _lhs.mul_(torch.sigmoid(_lhs)).mul_(_rhs)
8284- _scale_chunk = scale[_row_start:_row_end] if torch.is_tensor(scale) and scale.numel() > 1 else scale
8285- _lhs.mul_(_scale_chunk).clamp_(min=-float(max_val), max=float(max_val))
8286- _out[_row_start:_row_end] = _lhs.to(x.dtype)
8287- result = [_out.reshape(*x.shape[:-1], _hidden)]
8275+ _scale_exp = torch.tensor(float(scale), dtype=torch.float32, device=x.device)
8276+ while _scale_exp.dim() < _gate.dim():
8277+ _scale_exp = _scale_exp.unsqueeze(-1)
8278+ _swiglu = (_gate * torch.sigmoid(_gate)) * _val
8279+ result = [(_swiglu * _scale_exp).to(x.dtype)]
82888280
82898281
82908282elif op_name == "fused_swiglu_scale_clamp_bwd":
82918283 # _run_custom_op("fused_swiglu_scale_clamp_bwd", x, scale, dy, max_val)
8292- # arg1=x (original fwd input [..., 2D]), arg2=scale, arg3=dy (grad of output [..., D]),
8293- # arg4=max_val. Returns [dx, d_scale] to match Paddle output.
8284+ # clamp 语义对齐 PaddleFleet fusions/fused_swiglu_scale.py::
8285+ # fused_swiglu_scale_backward, 精度/舍入点对齐 CUDA kernel
8286+ # VectorizedFusedSwiGLUBwd (kHasClamp=true):
8287+ # g_eff = min(g, cv); v_eff = clamp(v, -cv, cv)
8288+ # g_mask = (g <= cv); v_mask = (-cv <= v <= cv) # fp32 掩码, 边界处梯度通过
8289+ # d_u = dout * s # 全程 fp32
8290+ # d_v = (T)(d_u * silu_g * v_mask)
8291+ # d_g = (T)(d_u * sig * (1 + g_eff*(1-sig)) * v_eff * g_mask)
8292+ # d_scale = (ScaleT)sum_fp32( (T)swiglu * (ScaleT)dout )
8293+ # kernel 特意把 fp32 swiglu 先压回 x.dtype 再与 dout 相乘, 用 fp32 累加
82948294 x = arg1 # shape [..., 2D] (original forward input)
82958295 scale = arg2 # scalar or tensor [..., 1]
82968296 dy = arg3 # shape [..., D] (gradient of forward output)
82978297 max_val = arg4 # scalar
8298- _hidden = dy.shape[-1]
8299- _outer = dy.numel() // _hidden
8300- _x_2d = x.reshape(_outer, _hidden * 2)
8301- _dy_2d = dy.reshape(_outer, _hidden)
8302- _dx = torch.empty_like(_x_2d)
8303- _d_scale = torch.empty((_outer, 1), dtype=torch.float32, device=x.device)
8298+ _cv = float(max_val)
8299+ _hidden = x.shape[-1] // 2
8300+ _x_fp32 = x.to(torch.float32)
8301+ _gate_raw = _x_fp32[..., :_hidden]
8302+ _val_raw = _x_fp32[..., _hidden:]
8303+ _gate = torch.clamp(_gate_raw, max=_cv)
8304+ _val = torch.clamp(_val_raw, min=-_cv, max=_cv)
8305+ # kernel 中掩码是 float, 保持 fp32 避免额外的 dtype 提升
8306+ _g_mask = (_gate_raw <= _cv).to(torch.float32)
8307+ _v_mask = ((_val_raw <= _cv) & (_val_raw >= -_cv)).to(torch.float32)
8308+ _sig = torch.sigmoid(_gate)
8309+ _silu = _gate * _sig
8310+ _swiglu_val = _silu * _val
83048311 if torch.is_tensor(scale):
8305- scale_v = scale.view(*scale.shape[:-1], 1) if scale.dim() > 1 else scale.unsqueeze(-1)
8312+ _scale_dtype = scale.dtype
8313+ _scale_exp = scale.to(torch.float32)
83068314 else:
8307- scale_v = float(scale)
8308- _workspace_bytes = 32 << 30
8309- _bytes_per_row = max(1, _hidden * (4 * 10 + x.element_size() * 2))
8310- _row_chunk = max(1, min(_outer, _workspace_bytes // _bytes_per_row))
8311- with torch.no_grad():
8312- for _row_start in range(0, _outer, _row_chunk):
8313- _row_end = min(_outer, _row_start + _row_chunk)
8314- _lhs = _x_2d[_row_start:_row_end, :_hidden].to(torch.float32)
8315- _rhs = _x_2d[_row_start:_row_end, _hidden:].to(torch.float32)
8316- _dy = _dy_2d[_row_start:_row_end].to(torch.float32)
8317- _scale_chunk = scale_v[_row_start:_row_end] if torch.is_tensor(scale_v) and scale_v.numel() > 1 else scale_v
8318- _sigmoid = torch.sigmoid(_lhs)
8319- _silu = _lhs * _sigmoid
8320- _pre_clamp = _silu * _rhs * _scale_chunk
8321- _dy.mul_((_pre_clamp.abs() < float(max_val)).to(torch.float32))
8322- _d_scale[_row_start:_row_end] = (_dy * _silu * _rhs).sum(dim=-1, keepdim=True)
8323- _dx[_row_start:_row_end, _hidden:] = (_dy * _silu * _scale_chunk).to(x.dtype)
8324- _lhs.sub_(_silu).add_(1.0).mul_(_sigmoid).mul_(_rhs).mul_(_dy).mul_(_scale_chunk)
8325- _dx[_row_start:_row_end, :_hidden] = _lhs.to(x.dtype)
8326- dx = _dx.reshape(x.shape)
8327- d_scale = _d_scale.reshape(*x.shape[:-1], 1)
8328- if torch.is_tensor(scale):
8329- d_scale = d_scale.to(scale.dtype)
8315+ _scale_dtype = torch.float32
8316+ _scale_exp = torch.tensor(float(scale), dtype=torch.float32, device=x.device)
8317+ while _scale_exp.dim() < dy.dim():
8318+ _scale_exp = _scale_exp.unsqueeze(-1)
8319+ _d_u = dy * _scale_exp
8320+ _d_val = _d_u * _silu * _v_mask
8321+ # kernel 里 1.0f + g_eff*(1.0f-sig) 会被 nvcc 收缩成单条 FMA(只舍入一次),
8322+ # 用 addcmul 走同一条 fused 路径, 而不是 mul+add 两次舍入。
8323+ _d_gate = (
8324+ _d_u
8325+ * _sig
8326+ * torch.addcmul(torch.ones((), dtype=torch.float32, device=x.device), _gate, 1.0 - _sig)
8327+ * _val
8328+ * _g_mask
8329+ )
8330+ dx = torch.cat([_d_gate, _d_val], dim=-1).to(x.dtype)
8331+ # d_scale: fp32 swiglu 先压回 x.dtype, dout 转 scale dtype, 乘积用 fp32 累加,
8332+ # 最后一次性 cast 回 scale dtype(与 kernel 的 shared float 归约一致)
8333+ d_scale = (
8334+ (_swiglu_val.to(x.dtype) * dy.to(_scale_dtype))
8335+ .to(torch.float32)
8336+ .sum(dim=-1, keepdim=True)
8337+ .to(_scale_dtype)
8338+ )
83308339 result = [dx, d_scale]
83318340
83328341elif op_name in ("fused_swiglu_probs_bwd", "paddlefleet_fused_swiglu_probs_bwd"):
0 commit comments