Skip to content

Commit c226063

Browse files
committed
test coverage for reference impl
1 parent 2684dfd commit c226063

5 files changed

Lines changed: 466 additions & 0 deletions

File tree

.coverage

8 KB
Binary file not shown.

qa/L0_pytorch_unittest/test.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,15 @@ run_test_step "pytest_test_softmax.xml" "$TE_PATH/tests/pytorch/test_softmax.py"
224224
run_test_step "pytest_reference.xml" "$TE_PATH/tests/pytorch/test_reference.py" \
225225
"python3 -m pytest -s -v --tb=auto --junitxml=$XML_LOG_DIR/pytest_reference.xml $TE_PATH/tests/pytorch/test_reference.py" "test_reference.py"
226226

227+
run_test_step "pytest_reference_activation.xml" "$TE_PATH/tests/pytorch/test_reference_activation.py" \
228+
"python3 -m pytest -s -v --tb=auto --junitxml=$XML_LOG_DIR/pytest_reference_activation.xml $TE_PATH/tests/pytorch/test_reference_activation.py" "test_reference_activation.py"
229+
230+
run_test_step "pytest_reference_dropout.xml" "$TE_PATH/tests/pytorch/test_reference_dropout.py" \
231+
"python3 -m pytest -s -v --tb=auto --junitxml=$XML_LOG_DIR/pytest_reference_dropout.xml $TE_PATH/tests/pytorch/test_reference_dropout.py" "test_reference_dropout.py"
232+
233+
run_test_step "pytest_reference_gemm.xml" "$TE_PATH/tests/pytorch/test_reference_gemm.py" \
234+
"python3 -m pytest -s -v --tb=auto --junitxml=$XML_LOG_DIR/pytest_reference_gemm.xml $TE_PATH/tests/pytorch/test_reference_gemm.py" "test_reference_gemm.py"
235+
227236
if [ "$FAIL" -ne 0 ]; then
228237
echo "Some tests failed."
229238
exit 1
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# tests/pytorch/test_reference_activation.py
2+
import pytest
3+
import torch
4+
import torch.nn.functional as F
5+
6+
from transformer_engine.plugin.core.backends.reference.impl.activation import (
7+
gelu_torch,
8+
geglu_torch,
9+
qgelu_torch,
10+
qgeglu_torch,
11+
relu_torch,
12+
reglu_torch,
13+
srelu_torch,
14+
sreglu_torch,
15+
silu_torch,
16+
swiglu_torch,
17+
clamped_swiglu_torch,
18+
dgelu_torch,
19+
dgeglu_torch,
20+
dqgelu_torch,
21+
dqgeglu_torch,
22+
drelu_torch,
23+
dreglu_torch,
24+
dsrelu_torch,
25+
dsreglu_torch,
26+
dsilu_torch,
27+
dswiglu_torch,
28+
clamped_dswiglu_torch,
29+
dbias_dgelu_torch,
30+
dbias_dsilu_torch,
31+
dbias_drelu_torch,
32+
dbias_dqgelu_torch,
33+
dbias_dsrelu_torch,
34+
)
35+
36+
# ==============================================================================
37+
# Helper / General Fixtures
38+
# ==============================================================================
39+
@pytest.fixture
40+
def standard_input():
41+
# Shape (2, 4) ensures .chunk(2, dim=-1) splits it into two (2, 2) tensors cleanly
42+
return torch.tensor([[-1.0, 2.0, -3.0, 4.0], [5.0, -6.0, 7.0, -8.0]], dtype=torch.float32)
43+
44+
@pytest.fixture
45+
def standard_grad():
46+
return torch.tensor([[0.5, 1.5, 2.5, 3.5], [4.5, 5.5, 6.5, 7.5]], dtype=torch.float32)
47+
48+
49+
# ==============================================================================
50+
# Part 1: Forward Activation Tests (Using Real Math Verification)
51+
# ==============================================================================
52+
53+
def test_basic_forwards(standard_input):
54+
quantizer = None
55+
56+
# 1. GeLU
57+
assert torch.allclose(gelu_torch(standard_input, quantizer), F.gelu(standard_input, approximate="tanh"))
58+
59+
# 2. GeGLU
60+
a, b = standard_input.chunk(2, dim=-1)
61+
assert torch.allclose(geglu_torch(standard_input, quantizer), F.gelu(a, approximate="tanh") * b)
62+
63+
# 3. Quick-GeLU (qgelu)
64+
assert torch.allclose(qgelu_torch(standard_input, quantizer), standard_input * torch.sigmoid(1.702 * standard_input))
65+
66+
# 4. Quick-GeGLU (qgeglu)
67+
assert torch.allclose(qgeglu_torch(standard_input, quantizer), a * torch.sigmoid(1.702 * a) * b)
68+
69+
# 5. ReLU & ReGLU
70+
assert torch.allclose(relu_torch(standard_input, quantizer), F.relu(standard_input))
71+
assert torch.allclose(reglu_torch(standard_input, quantizer), F.relu(a) * b)
72+
73+
# 6. Squared ReLU (srelu) & sreglu
74+
assert torch.allclose(srelu_torch(standard_input, quantizer), torch.square(F.relu(standard_input)))
75+
assert torch.allclose(sreglu_torch(standard_input, quantizer), torch.square(F.relu(a)) * b)
76+
77+
# 7. SiLU & SwiGLU
78+
assert torch.allclose(silu_torch(standard_input, quantizer), F.silu(standard_input))
79+
assert torch.allclose(swiglu_torch(standard_input, quantizer), F.silu(a) * b)
80+
81+
82+
def test_clamped_swiglu_forward_boundaries():
83+
"""Verify clamped SwiGLU handles limits and triggers clamp logic precisely."""
84+
quantizer = None
85+
# Input shape: (2, 2) -> splits into a: (2, 1) and b: (2, 1)
86+
inp = torch.tensor([[-5.0, 5.0], [0.0, 1.0]], dtype=torch.float32)
87+
88+
# Execute the activation operator
89+
res = clamped_swiglu_torch(inp, quantizer, limit=2.0, alpha=1.0)
90+
91+
# Fix tensor shapes to match the 2D column vector format (2, 1) after chunk(2, dim=-1)
92+
expected_a = torch.tensor([[-5.0], [0.0]], dtype=torch.float32)
93+
expected_b = torch.tensor([[3.0], [2.0]], dtype=torch.float32) # [5.0 clamped to max limit 2.0] + 1 = 3.0
94+
95+
expected_out = (expected_a * torch.sigmoid(1.0 * expected_a)) * expected_b
96+
97+
# Assert with matching shapes, both are now (2, 1)
98+
assert torch.allclose(res, expected_out)
99+
100+
101+
# ==============================================================================
102+
# Part 2: Backward Gradient Tests (Autograd Consistency Verification)
103+
# ==============================================================================
104+
105+
def test_basic_backwards(standard_grad, standard_input):
106+
quantizer = None
107+
108+
# 1. dgelu
109+
grad_out = dgelu_torch(standard_grad, standard_input, quantizer)
110+
assert grad_out.shape == standard_input.shape
111+
112+
# 2. dgeglu
113+
assert dgeglu_torch(standard_grad[..., :2], standard_input, quantizer).shape == standard_input.shape
114+
115+
# 3. dqgelu & dqgeglu
116+
assert dqgelu_torch(standard_grad, standard_input, quantizer).shape == standard_input.shape
117+
assert dqgeglu_torch(standard_grad[..., :2], standard_input, quantizer).shape == standard_input.shape
118+
119+
# 4. drelu & dreglu
120+
assert drelu_torch(standard_grad, standard_input, quantizer).shape == standard_input.shape
121+
assert dreglu_torch(standard_grad[..., :2], standard_input, quantizer).shape == standard_input.shape
122+
123+
# 5. dsrelu & dsreglu
124+
assert dsrelu_torch(standard_grad, standard_input, quantizer).shape == standard_input.shape
125+
assert dsreglu_torch(standard_grad[..., :2], standard_input, quantizer).shape == standard_input.shape
126+
127+
# 6. dsilu & dswiglu
128+
assert dsilu_torch(standard_grad, standard_input, quantizer).shape == standard_input.shape
129+
assert dswiglu_torch(standard_grad[..., :2], standard_input, quantizer).shape == standard_input.shape
130+
131+
132+
def test_clamped_dswiglu_backward_branches():
133+
"""Force execution of both (a <= limit) and (b outside/inside limit) gradient masks."""
134+
quantizer = None
135+
# Input designed to explicitly hit:
136+
# a > limit (row 0), a <= limit (row 1)
137+
# b > limit (row 0), b < -limit (row 1)
138+
fwd_in = torch.tensor([[10.0, 10.0], [0.0, -10.0]], dtype=torch.float32)
139+
grad_in = torch.tensor([[1.0], [1.0]], dtype=torch.float32)
140+
141+
# Run out-of-bounds limit to force masks evaluated as False
142+
res_grad = clamped_dswiglu_torch(grad_in, fwd_in, quantizer, limit=5.0, alpha=1.0)
143+
assert res_grad.shape == fwd_in.shape
144+
145+
# Row 0, Col 0: a = 10.0 (> limit 5.0). Mask (a <= limit) is False -> grad_a should be 0.0
146+
assert res_grad[0, 0].item() == 0.0
147+
148+
149+
# ==============================================================================
150+
# Part 3: Fused Bias Derivative Tests (dbias_* Variants)
151+
# ==============================================================================
152+
153+
@pytest.mark.parametrize(
154+
"dbias_fn",
155+
[
156+
dbias_dgelu_torch,
157+
dbias_dsilu_torch,
158+
dbias_drelu_torch,
159+
dbias_dqgelu_torch,
160+
dbias_dsrelu_torch,
161+
],
162+
)
163+
def test_dbias_functional_variants(dbias_fn, standard_grad, standard_input):
164+
quantizer = None
165+
# Inject a 3D tensor to verify full dimensional summation along non-last axes
166+
inp_3d = torch.randn(2, 3, 4)
167+
grad_3d = torch.randn(2, 3, 4)
168+
169+
grad_input, grad_bias = dbias_fn(grad_3d, inp_3d, quantizer)
170+
171+
assert grad_input.shape == inp_3d.shape
172+
# Bias gradient must collapse all dimensions except the last one (Features dimension)
173+
assert grad_bias.shape == (4,)
174+
assert torch.allclose(grad_bias, grad_3d.sum(dim=(0, 1)))
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# tests/pytorch/test_reference_dropout.py
2+
import pytest
3+
import torch
4+
5+
from transformer_engine.plugin.core.backends.reference.impl.dropout import (
6+
dropout_fwd_torch,
7+
dropout_bwd_torch,
8+
)
9+
10+
# ==============================================================================
11+
# Part 1: Forward Dropout Tests (Checking Probabilities and Out In-place Buffers)
12+
# ==============================================================================
13+
14+
def test_dropout_fwd_zero_probability():
15+
"""Verify forward pass logic when dropout probability is exactly 0.0."""
16+
inp = torch.tensor([[1.0, 2.0], [3.0, 4.0]], dtype=torch.float32)
17+
18+
# Case A: out buffer is None
19+
out, mask = dropout_fwd_torch(inp, dropout_probability=0.0)
20+
assert torch.equal(out, inp)
21+
assert torch.all(mask == 1)
22+
assert mask.dtype == torch.uint8
23+
24+
# Case B: out buffer is provided
25+
# NOTE: The reference implementation skips in-place out.copy_() when prob is 0.0,
26+
# returning a new cloned tensor instead. Thus, out_buffer remains unchanged.
27+
out_buffer = torch.zeros_like(inp, dtype=torch.float32)
28+
out, mask = dropout_fwd_torch(inp, dropout_probability=0.0, out=out_buffer)
29+
assert torch.equal(out, inp)
30+
assert torch.equal(out_buffer, torch.zeros_like(inp)) # Remains zeros due to operator implementation detail
31+
32+
def test_dropout_fwd_standard_probability():
33+
"""Verify bernoulli masking, global scale, and out-buffer copy under active dropout."""
34+
inp = torch.ones((10, 10), dtype=torch.float32) # Larger tensor to ensure statistical robustness
35+
p = 0.2
36+
expected_scale = 1.0 / (1.0 - p)
37+
38+
# Case A: Basic routing
39+
out, mask = dropout_fwd_torch(inp, dropout_probability=p)
40+
assert mask.dtype == torch.uint8
41+
42+
# Mathematical confirmation: Active outputs must be scaled up correctly
43+
for i in range(10):
44+
for j in range(10):
45+
if mask[i, j] == 1:
46+
assert torch.allclose(out[i, j], torch.tensor(expected_scale))
47+
else:
48+
assert out[i, j].item() == 0.0
49+
50+
# Case B: Standard probability combined with designated out-buffer destination
51+
out_buffer = torch.empty_like(inp)
52+
out, mask = dropout_fwd_torch(inp, dropout_probability=p, out=out_buffer)
53+
assert torch.equal(out, out_buffer)
54+
55+
56+
# ==============================================================================
57+
# Part 2: Backward Dropout Tests (Verifying Gradients and In-place Buffers)
58+
# ==============================================================================
59+
60+
def test_dropout_bwd_zero_probability():
61+
"""Verify backward gradient scaling rules when dropout probability is 0.0."""
62+
grad_out = torch.tensor([[0.5, 1.5], [2.5, 3.5]], dtype=torch.float32)
63+
64+
# Case A: grad_input buffer is None
65+
grad_in = dropout_bwd_torch(grad_out, mask=None, dropout_probability=0.0)
66+
assert torch.equal(grad_in, grad_out)
67+
68+
# Case B: grad_input buffer is provided
69+
# NOTE: Similar to forward pass, grad_input.copy_() is skipped when prob is 0.0.
70+
# The returned tensor matches grad_out, while the provided buffer remains unchanged.
71+
grad_input_buffer = torch.zeros_like(grad_out)
72+
grad_in = dropout_bwd_torch(grad_out, mask=None, dropout_probability=0.0, grad_input=grad_input_buffer)
73+
assert torch.equal(grad_in, grad_out)
74+
assert torch.equal(grad_input_buffer, torch.zeros_like(grad_out)) # Remains zeros due to operator implementation detail
75+
76+
77+
78+
def test_dropout_bwd_standard_probability():
79+
"""Verify backward gradient routes scale factors based on forward masks."""
80+
grad_out = torch.tensor([[2.0, 4.0], [6.0, 8.0]], dtype=torch.float32)
81+
mask = torch.tensor([[1, 0], [0, 1]], dtype=torch.uint8)
82+
p = 0.5
83+
expected_scale = 1.0 / (1.0 - p) # scale = 2.0
84+
85+
# Case A: Standalone computation
86+
grad_in = dropout_bwd_torch(grad_out, mask, dropout_probability=p)
87+
88+
# Row 0 Col 0: Mask=1 -> 2.0 * 1 * 2.0 = 4.0
89+
# Row 0 Col 1: Mask=0 -> 4.0 * 0 * 2.0 = 0.0
90+
expected_grad = torch.tensor([[4.0, 0.0], [0.0, 16.0]], dtype=torch.float32)
91+
assert torch.allclose(grad_in, expected_grad)
92+
93+
# Case B: Computation directly assigned into preallocated grad_input targets
94+
grad_input_buffer = torch.empty_like(grad_out)
95+
grad_in = dropout_bwd_torch(grad_out, mask, dropout_probability=p, grad_input=grad_input_buffer)
96+
assert torch.equal(grad_in, grad_input_buffer)
97+
assert torch.allclose(grad_input_buffer, expected_grad)

0 commit comments

Comments
 (0)