Skip to content

Commit b2f9c78

Browse files
fix test batch accuracy to 100%
1 parent bcbb4bc commit b2f9c78

2 files changed

Lines changed: 87 additions & 45 deletions

File tree

test/tpu/torch_backend.py

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import torch
2-
from test_tpu import matmul
2+
from test_tpu import matmul, reset_dut
33
from torch._inductor.compile_fx import compile_fx
44
from torch._dynamo import register_backend
55
from torch.library import custom_op, register_fake
@@ -17,52 +17,75 @@
1717
def tpu_matmul(a_q: Tensor, b_q: Tensor, bias: Optional[Tensor] = None,
1818
a_scale: Optional[float] = None, b_scale: Optional[float] = None,
1919
a_zero: Optional[int] = None, b_zero: Optional[int] = None) -> Tensor:
20-
# Prepare centered int8 inputs for the TPU:
21-
# 1) convert to int32 so subtraction is safe
22-
# 2) subtract zero_point to center around 0
23-
# 3) clamp to int8 range and cast back to int8 for the hardware
20+
21+
if a_zero is None:
22+
a_zero = 0
23+
if b_zero is None:
24+
b_zero = 0
25+
26+
# Convert to int32 for safe arithmetic
2427
a_i32 = a_q.to(torch.int32)
2528
b_i32 = b_q.to(torch.int32)
26-
27-
if a_zero is not None:
28-
a_center = a_i32 - int(a_zero)
29-
else:
30-
a_center = a_i32
31-
if b_zero is not None:
32-
b_center = b_i32 - int(b_zero)
29+
30+
# Subtract zero points
31+
a_centered = a_i32 - int(a_zero)
32+
b_centered = b_i32 - int(b_zero)
33+
34+
# For uint8-style quantization (zero_point=-128), shift to signed range
35+
shift_amount = 0
36+
if a_zero == -128:
37+
shift_amount = 128
38+
a_hw = (a_centered - shift_amount).clamp(-128, 127).to(torch.int8)
3339
else:
34-
b_center = b_i32
35-
36-
a_hw = a_center.clamp(-128, 127).to(torch.int8)
37-
b_hw = b_center.clamp(-128, 127).to(torch.int8)
38-
39-
# Call TPU: provide int8 inputs; TPU returns int32 accumulators
40+
a_hw = a_centered.clamp(-128, 127).to(torch.int8)
41+
42+
b_hw = b_centered.clamp(-128, 127).to(torch.int8)
43+
44+
# Call TPU
4045
future = concurrent.futures.Future()
4146
async def wrapper():
4247
try:
48+
await reset_dut(dut)
4349
result_int32 = await matmul(dut, a_hw, b_hw, transpose=True, is_torch=True)
4450
future.set_result(result_int32)
4551
except Exception as e:
4652
future.set_exception(e)
4753
cocotb.start_soon(wrapper())
4854
result_int32 = future.result()
49-
50-
# Dequantize to float using scale product (accumulators are int32)
51-
if a_scale is None or b_scale is None:
52-
out = result_int32.to(torch.float32)
53-
else:
55+
56+
# Apply correction for the shift
57+
if shift_amount != 0:
58+
correction = shift_amount * b_centered.sum(dim=1, dtype=torch.int32)
59+
correction = correction.view(1, -1).expand_as(result_int32)
60+
result_int32 = result_int32 + correction
61+
62+
# Convert to float and scale
63+
result_float = result_int32.to(torch.float32)
64+
65+
if a_scale is not None and b_scale is not None:
5466
scale = float(a_scale) * float(b_scale)
55-
out = result_int32.to(torch.float32) * scale
56-
67+
out = result_float * scale
68+
else:
69+
out = result_float
70+
71+
# Add bias
5772
if bias is not None:
5873
out = out + bias
59-
6074
return out
6175

6276
@register_fake("tpu::matmul")
6377
def tpu_matmul_abstract(a: Tensor, b: Tensor, bias: Optional[Tensor] = None, *args, **kwargs) -> Tensor:
64-
M, N = a.shape[-1], b.shape[-2]
65-
out = a.new_zeros(a.shape[:-1] + (N,), dtype=torch.float32)
78+
# a: (..., K) where K is input features
79+
# b: (N, K) where N is output features (weight matrix, will be transposed)
80+
# output: (..., N) after computing a @ b.T
81+
82+
# b.shape[0] is N (number of output features)
83+
N = b.shape[0]
84+
85+
# Output shape: same batch dims as a, but last dim is N
86+
out_shape = list(a.shape[:-1]) + [N]
87+
out = a.new_zeros(out_shape, dtype=torch.float32)
88+
6689
if bias is not None:
6790
out = out + bias
6891
return out
@@ -76,14 +99,15 @@ def _backend(gm: torch.fx.GraphModule, example_inputs):
7699
# print("\n=== FX graph received ===")
77100
# gm.graph.print_tabular()
78101

79-
# ---- replace every linear but try to preserve quantized buffers + scale info ----
102+
# Replace linear operations with TPU matmul
80103
dequant_op = torch.ops.quantized_decomposed.dequantize_per_tensor.default
104+
81105
for node in list(gm.graph.nodes):
82106
if node.target == torch.ops.aten.linear.default:
83107
x_node, w_node, bias = node.args
84108

85109
def unwrap_dequant(n):
86-
# If node is a dequantize_per_tensor node, return (q_tensor, scale, zero_point, node)
110+
# If node is a dequantize_per_tensor node, return quantization params
87111
if isinstance(n, torch.fx.Node) and n.target == dequant_op:
88112
# args: (quantized_tensor, scale, zero_point, min, max, dtype)
89113
q_tensor = n.args[0]
@@ -99,23 +123,25 @@ def unwrap_dequant(n):
99123
if x_un and w_un:
100124
q_x, x_scale, x_zp, x_deq_node = x_un
101125
q_w, w_scale, w_zp, w_deq_node = w_un
126+
127+
# Verify zero points are 0 for symmetric quantization
128+
print(f"Replacing linear: x_zp={x_zp}, w_zp={w_zp}")
129+
102130
new_node = gm.graph.call_function(
103131
torch.ops.tpu.matmul,
104132
args=(q_x, q_w, bias, x_scale, w_scale, x_zp, w_zp),
105133
)
106134
node.replace_all_uses_with(new_node)
107135
gm.graph.erase_node(node)
108-
# try to erase the now-unused dequantize nodes
109-
try:
136+
137+
# Clean up unused dequantize nodes
138+
if len(x_deq_node.users) == 0:
110139
gm.graph.erase_node(x_deq_node)
111-
except Exception:
112-
pass
113-
try:
140+
if len(w_deq_node.users) == 0:
114141
gm.graph.erase_node(w_deq_node)
115-
except Exception:
116-
pass
117142
else:
118-
# Fallback: if inputs are already raw tensors/float, call tpu.matmul with them
143+
# Fallback for non-quantized tensors
144+
print(f"Warning: Linear layer not quantized, using fallback")
119145
new_node = gm.graph.call_function(
120146
torch.ops.tpu.matmul,
121147
args=(x_node, w_node, bias),

test/tpu/train_qat_model.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
get_symmetric_quantization_config,
1515
XNNPACKQuantizer,
1616
)
17-
17+
from test_tpu import reset_dut
1818
import cocotb
1919
from cocotb.clock import Clock
2020
import numpy as np
@@ -35,7 +35,18 @@ def forward(self, x):
3535
x = self.relu(x)
3636
x = self.fc2(x)
3737
return x
38-
38+
39+
def compute_accuracy(model, dataloader):
40+
correct = 0
41+
total = 0
42+
with torch.no_grad():
43+
for images, labels in dataloader:
44+
outputs = model(images)
45+
_, predicted = torch.max(outputs.data, 1)
46+
total += labels.size(0)
47+
correct += (predicted == labels).sum().item()
48+
return correct / total
49+
3950
def get_quantized_model():
4051
transform = transforms.Compose([transforms.ToTensor()])
4152
train_ds = torchvision.datasets.MNIST(root='./data', train=True,
@@ -70,18 +81,22 @@ def get_quantized_model():
7081
criterion = nn.CrossEntropyLoss()
7182
optimizer = torch.optim.SGD(prepared_model.parameters(), lr=0.01, momentum=0.9)
7283

73-
prepared_model = move_exported_model_to_train(prepared_model)
74-
for epoch in range(10):
84+
for epoch in range(3):
85+
prepared_model = move_exported_model_to_train(prepared_model)
86+
7587
for images, labels in train_loader:
7688
optimizer.zero_grad()
77-
out = prepared_model(images) # Input is 1x1x28x28, the graph handles the flatten.
89+
out = prepared_model(images)
7890
loss = criterion(out, labels)
7991
loss.backward()
8092
optimizer.step()
8193

94+
# ---- Accuracy after epoch ----
95+
prepared_model = move_exported_model_to_eval(prepared_model)
96+
acc = compute_accuracy(prepared_model, train_loader)
97+
print(f"Epoch {epoch+1} training accuracy: {acc:.4f}")
98+
8299
print("Training over")
83-
84-
prepared_model = move_exported_model_to_eval(prepared_model)
85100

86101
# --- FULL TORCHAO PT2E Convert Step ---
87102
print("Converting prepared model to quantized model...")
@@ -98,6 +113,7 @@ async def tpu_torch_test(dut):
98113
model = get_quantized_model()
99114
clock = Clock(dut.clk, 20, units="ns")
100115
cocotb.start_soon(clock.start())
116+
await reset_dut(dut)
101117

102118
# compile it with backend
103119
from torch_backend import make_backend

0 commit comments

Comments
 (0)