Skip to content

Commit 45d350a

Browse files
Merge branch 'main' into fix/dequantize-negative-axis
2 parents 537ef3e + a43cc84 commit 45d350a

34 files changed

Lines changed: 3193 additions & 786 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
run: |
3535
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
3636
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
37-
- run: uv run --extra test pytest tests/ -n auto -m "not slow"
37+
- run: uv run --extra test pytest tests/ -n auto -m "not slow and not dsl"

coreai_torch/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
"""Version information for coreai-torch."""
77

8-
__version__ = "0.4.0"
8+
__version__ = "0.4.1"

coreai_torch/_aten_to_core.py

Lines changed: 144 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
from ._utils import (
5454
get_operands as _get_operands,
5555
)
56+
from ._utils import (
57+
replace_pad_with_mode as _replace_pad_with_mode,
58+
)
5659

5760
INT32_MAX: int = 2147483647
5861

@@ -644,20 +647,28 @@ def replace_arange_start_step(
644647
else coreai.constant(1, dtype=start.type.element_type)
645648
)
646649

647-
# coreai.range_ requires scalar (rank-0) operands that share an element
648-
# type. aten.arange promotes mixed-type scalars internally; we replicate
649-
# that here by squeezing each operand to rank-0 and casting to the FX
650-
# node's output dtype before the op.
650+
# When ALL operands are integer-typed, keep them as si32 so coreai.range_
651+
# can infer a static output shape, then cast the result to the requested
652+
# dtype. If any operand is float (e.g. arange(0, 5, 0.5)), fall back to
653+
# target_type — truncating a float step to si32 would corrupt the values.
651654
target_type = get_output_element_type_from_node(node)
655+
si32 = IntegerType.get_signed(32)
656+
all_integer = all(
657+
isinstance(v.type.element_type, IntegerType) for v in (start, end, step)
658+
)
659+
range_type = si32 if all_integer else target_type
652660

653661
def to_scalar(v: Value) -> Value:
654662
if v.type.rank > 0:
655663
v = coreai.shrink_dims(v, list(range(v.type.rank)))
656-
if v.type.element_type != target_type:
657-
v = coreai.cast(v, target_type)
664+
if v.type.element_type != range_type:
665+
v = coreai.cast(v, range_type)
658666
return v
659667

660-
return coreai.range_(to_scalar(start), to_scalar(end), to_scalar(step))
668+
result = coreai.range_(to_scalar(start), to_scalar(end), to_scalar(step))
669+
if result.type.element_type != target_type:
670+
result = coreai.cast(result, target_type)
671+
return result
661672

662673

663674
def replace_batch_norm(
@@ -1109,6 +1120,20 @@ def replace_constant_pad_nd(
11091120
return result
11101121

11111122

1123+
def replace_reflection_pad(
1124+
values_map: dict[str, Value], node: fx.Node, loc: Location
1125+
) -> Value:
1126+
"""aten.reflection_pad{1,2,3}d.default -> coreai.pad<reflect>."""
1127+
return _replace_pad_with_mode(values_map, node, loc, "reflect")
1128+
1129+
1130+
def replace_replication_pad(
1131+
values_map: dict[str, Value], node: fx.Node, loc: Location
1132+
) -> Value:
1133+
"""aten.replication_pad{1,2,3}d.default -> coreai.pad<replicate>."""
1134+
return _replace_pad_with_mode(values_map, node, loc, "replicate")
1135+
1136+
11121137
def _conv_transpose(
11131138
x: Value,
11141139
weight: Value,
@@ -1522,6 +1547,111 @@ def replace_argmax(values_map: dict[str, Value], node: fx.Node, loc: Location) -
15221547
return result if keepdim else coreai.shrink_dims(result, [dim])
15231548

15241549

1550+
def replace_atan2(values_map: dict[str, Value], node: fx.Node, loc: Location) -> Value:
1551+
"""Lower atan2(y, x) using atan(y/x) with quadrant correction.
1552+
1553+
CoreAI has no native atan2, so it is decomposed as:
1554+
- x != 0, finite: atan(y/x) adjusted by ±π for the correct quadrant.
1555+
- x == +0: ±π/2 for non-zero y, 0 for y = 0.
1556+
- x == -0: ±π for all y (including ±0 → ±π per IEEE-754).
1557+
- both infinite: ±π/4 or ±3π/4 per IEEE-754.
1558+
1559+
Signed-zero handling: IEEE-754 treats -0.0 as distinct from +0.0 for atan2
1560+
(e.g. atan2(-0, -1) = -π, not +π). The 1/v trick — 1/-0.0 = -inf — is used
1561+
to detect the sign bit of zero inputs so that y_neg and x_neg are correct
1562+
for -0.0 inputs without misclassifying ±inf (which use the strict > path).
1563+
1564+
When x=0, x is replaced with 1 before the divide solely to avoid NaN/inf; that
1565+
intermediate result is discarded by the final where-select.
1566+
atan2(0, 0) = 0 by convention.
1567+
"""
1568+
y, x = _get_operands(values_map, node, [0, 1])
1569+
ele_type = x.type.element_type
1570+
1571+
zero = coreai.constant(0.0, dtype=ele_type)
1572+
one = coreai.constant(1.0, dtype=ele_type)
1573+
pi = coreai.constant(np.pi, dtype=ele_type)
1574+
neg_pi = coreai.constant(-np.pi, dtype=ele_type)
1575+
half_pi = coreai.constant(np.pi / 2.0, dtype=ele_type)
1576+
neg_half_pi = coreai.constant(-np.pi / 2.0, dtype=ele_type)
1577+
quarter_pi = coreai.constant(np.pi / 4.0, dtype=ele_type)
1578+
neg_quarter_pi = coreai.constant(-np.pi / 4.0, dtype=ele_type)
1579+
three_quarter_pi = coreai.constant(3.0 * np.pi / 4.0, dtype=ele_type)
1580+
neg_three_quarter_pi = coreai.constant(-3.0 * np.pi / 4.0, dtype=ele_type)
1581+
1582+
# ── signed-zero-aware sign predicates ─────────────────────────────────────
1583+
# 1 / -0.0 = -inf (IEEE-754), so (0 > 1/v) is True iff v = -0.0. Combine with
1584+
# the strict > predicate (handles ±inf and non-zero finites) via OR.
1585+
y_is_zero = coreai.broadcasting_equal(y, zero)
1586+
x_is_zero = coreai.broadcasting_equal(x, zero)
1587+
y_neg = coreai.broadcasting_or(
1588+
coreai.broadcasting_greater(zero, y),
1589+
coreai.broadcasting_and(
1590+
y_is_zero,
1591+
coreai.broadcasting_greater(zero, coreai.broadcasting_divide(one, y)),
1592+
),
1593+
)
1594+
x_neg = coreai.broadcasting_or(
1595+
coreai.broadcasting_greater(zero, x),
1596+
coreai.broadcasting_and(
1597+
x_is_zero,
1598+
coreai.broadcasting_greater(zero, coreai.broadcasting_divide(one, x)),
1599+
),
1600+
)
1601+
x_is_neg_zero = coreai.broadcasting_and(
1602+
x_is_zero,
1603+
coreai.broadcasting_greater(zero, coreai.broadcasting_divide(one, x)),
1604+
)
1605+
1606+
# ── both-infinite branch ──────────────────────────────────────────────────
1607+
# atan(inf/inf) = atan(NaN) = NaN; handle before the divide.
1608+
pos_inf = coreai.constant(float("inf"), dtype=ele_type)
1609+
neg_inf = coreai.constant(float("-inf"), dtype=ele_type)
1610+
x_is_inf = coreai.broadcasting_or(
1611+
coreai.broadcasting_equal(x, pos_inf), coreai.broadcasting_equal(x, neg_inf)
1612+
)
1613+
y_is_inf = coreai.broadcasting_or(
1614+
coreai.broadcasting_equal(y, pos_inf), coreai.broadcasting_equal(y, neg_inf)
1615+
)
1616+
both_inf = coreai.broadcasting_and(x_is_inf, y_is_inf)
1617+
inf_result = coreai.broadcasting_where(
1618+
y_neg,
1619+
coreai.broadcasting_where(x_neg, neg_three_quarter_pi, neg_quarter_pi),
1620+
coreai.broadcasting_where(x_neg, three_quarter_pi, quarter_pi),
1621+
)
1622+
1623+
# ── x = 0 branch ──────────────────────────────────────────────────────────
1624+
# x = +0: ±π/2 for strictly ±y, 0 when y = 0.
1625+
# x = -0: ±π for all y (y_neg covers y = -0.0 via the 1/y trick above).
1626+
y_pos_strict = coreai.broadcasting_greater(y, zero)
1627+
y_neg_strict = coreai.broadcasting_greater(zero, y)
1628+
pos_x_zero_result = coreai.broadcasting_where(
1629+
y_pos_strict,
1630+
half_pi,
1631+
coreai.broadcasting_where(y_neg_strict, neg_half_pi, zero),
1632+
)
1633+
neg_x_zero_result = coreai.broadcasting_where(y_neg, neg_pi, pi)
1634+
zero_result = coreai.broadcasting_where(
1635+
x_is_neg_zero, neg_x_zero_result, pos_x_zero_result
1636+
)
1637+
1638+
# ── finite nonzero x branch ────────────────────────────────────────────────
1639+
# Avoid division by zero: substitute x = 1 when x = 0; result discarded by
1640+
# the outer where-select.
1641+
x_safe = coreai.broadcasting_where(x_is_zero, one, x)
1642+
base = coreai.atan(coreai.broadcasting_divide(y, x_safe))
1643+
correction = coreai.broadcasting_where(
1644+
y_neg,
1645+
coreai.broadcasting_sub(base, pi),
1646+
coreai.broadcasting_add(base, pi),
1647+
)
1648+
nonzero_result = coreai.broadcasting_where(x_neg, correction, base)
1649+
1650+
# ── combine ────────────────────────────────────────────────────────────────
1651+
result = coreai.broadcasting_where(x_is_zero, zero_result, nonzero_result)
1652+
return coreai.broadcasting_where(both_inf, inf_result, result)
1653+
1654+
15251655
def replace_gather(values_map: dict[str, Value], node: fx.Node, loc: Location) -> Value:
15261656
"""Converts aten.gather to coreai.gather_along_axis."""
15271657
x, index = _get_operands(values_map, node, [0, 2])
@@ -3440,6 +3570,7 @@ def sdpa_maskless(q: Value, k: Value, v: Value) -> Value:
34403570
"asin.default": replace_unary_ops,
34413571
"asinh.default": replace_unary_ops,
34423572
"atan.default": replace_unary_ops,
3573+
"atan2.default": replace_atan2,
34433574
"atanh.default": replace_unary_ops,
34443575
"_adaptive_avg_pool2d.default": replace_adaptive_avg_pool2d,
34453576
"_unsafe_view.default": replace_view,
@@ -3551,8 +3682,14 @@ def sdpa_maskless(q: Value, k: Value, v: Value) -> Value:
35513682
"prod.default": replace_prod_default,
35523683
"prod.dim_int": replace_prod_dim_int,
35533684
"reciprocal.default": replace_reciprocal,
3685+
"reflection_pad1d.default": replace_reflection_pad,
3686+
"reflection_pad2d.default": replace_reflection_pad,
3687+
"reflection_pad3d.default": replace_reflection_pad,
35543688
"relu.default": replace_unary_ops,
35553689
"remainder.Tensor": replace_remainder,
3690+
"replication_pad1d.default": replace_replication_pad,
3691+
"replication_pad2d.default": replace_replication_pad,
3692+
"replication_pad3d.default": replace_replication_pad,
35563693
"round.default": replace_unary_ops,
35573694
"round.decimals": replace_round_decimals,
35583695
"round": replace_unary_ops,

0 commit comments

Comments
 (0)