Nothing
# Chain tests: QLinearConv
# Quantized convolution: dequantize inputs → conv → requantize output.
# Covers: QLinearConv 2D, with bias, chain with activation, 3x3, boundary.
#
# Note: ggml needs C_in >= 2 for conv_2d to see 4D weight. All tests use
# input [1, 2, 4, 4] and weight [C_out, 2, kH, kW] to avoid scalar collapse.
run_onnx <- function(path, inputs, device = "cpu") {
m <- onnx_load(path, device = device)
res <- onnx_run(m, inputs)
res[[1]]
}
# Helper: make scalar F32 tensor initializer
make_scalar <- function(name, val) {
raw <- .float_bytes(val)
t <- .onnx_tensor(name, c(1L), 1L, raw)
vi <- .onnx_value_info(name, 1L, c(1L))
list(t = t, vi = vi)
}
# ── Minimal: QLinearConv 1x1, scale=1 zp=0 (identity-like) ──
test_that("chain qlinearconv: QLinearConv 1x1 (minimal)", {
# Input [1,2,4,4], weight [2,2,1,1] = identity across channels
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 1.0)
x_zp <- make_scalar("x_zp", 0.0)
# Weight: 2x2 identity kernel [C_out=2, C_in=2, 1, 1]
w_data <- c(1, 0, 0, 1)
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 1L, 1L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 1L, 1L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 1.0)
y_zp <- make_scalar("y_zp", 0.0)
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp"), "Y",
attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
graph <- .onnx_graph("test", list(qconv_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi, y_sc$vi, y_zp$vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
# 2 channels x 16 pixels = 32 values
x <- as.numeric(1:32)
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
# Identity conv: output = input
expect_equal(r, x, tolerance = 1e-3)
})
# ── QLinearConv with scale and zero-point ──────────────────
test_that("chain qlinearconv: QLinearConv with dequant/requant", {
# x_scale=0.1, x_zp=128 → dequant, identity conv, requant back
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 0.1)
x_zp <- make_scalar("x_zp", 128.0)
w_data <- c(1, 0, 0, 1)
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 1L, 1L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 1L, 1L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 0.1)
y_zp <- make_scalar("y_zp", 128.0)
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp"), "Y",
attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
graph <- .onnx_graph("test", list(qconv_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi, y_sc$vi, y_zp$vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
# All 138: dequant (138-128)*0.1=1.0, identity conv, requant 1.0/0.1+128=138
x <- rep(138, 32)
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
expect_equal(r, rep(138, 32), tolerance = 0.5)
})
# ── QLinearConv with bias ──────────────────────────────────
test_that("chain qlinearconv: QLinearConv with bias", {
# Identity conv + bias=10 per channel, scale=1 zp=0
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 1.0)
x_zp <- make_scalar("x_zp", 0.0)
w_data <- c(1, 0, 0, 1)
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 1L, 1L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 1L, 1L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 1.0)
y_zp <- make_scalar("y_zp", 0.0)
# Bias: [10.0, 10.0] for 2 output channels
bias_data <- c(10, 10)
bias_raw <- unlist(lapply(bias_data, .float_bytes))
bias_t <- .onnx_tensor("B", c(2L), 1L, bias_raw)
bias_vi <- .onnx_value_info("B", 1L, c(2L))
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp", "B"), "Y",
attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
graph <- .onnx_graph("test", list(qconv_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi,
y_sc$vi, y_zp$vi, bias_vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t, bias_t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
x <- rep(3, 32) # identity conv: 3 + bias 10 = 13
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
expect_equal(r, rep(13, 32), tolerance = 1e-2)
})
# ── QLinearConv → Relu chain ───────────────────────────────
test_that("chain qlinearconv: QLinearConv→Relu (activation chain)", {
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 0.1)
x_zp <- make_scalar("x_zp", 128.0)
w_data <- c(1, 0, 0, 1)
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 1L, 1L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 1L, 1L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 1.0)
y_zp <- make_scalar("y_zp", 0.0)
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp"), "qc",
attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
relu_node <- .onnx_node("Relu", "qc", "Y")
graph <- .onnx_graph("test", list(qconv_node, relu_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi, y_sc$vi, y_zp$vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
# Alternate below/above zp: 118→-1.0, 138→1.0
x <- rep(c(118, 138), 16)
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
# dequant→identity conv→relu: -1→0, 1→1
expected <- rep(c(0, 1), 16)
expect_equal(r, expected, tolerance = 1e-2)
})
# ── QLinearConv 3x3 with padding ───────────────────────────
test_that("chain qlinearconv: QLinearConv 3x3 same padding", {
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 1.0)
x_zp <- make_scalar("x_zp", 0.0)
# 3x3 identity-like: each output channel uses only its own input channel
# W shape [2, 2, 3, 3] = 36 values
# channel 0→0: all 1s, channel 1→0: all 0s, channel 0→1: all 0s, channel 1→1: all 1s
w_data <- c(rep(1, 9), rep(0, 9), # out_ch=0: in_ch=0 all 1, in_ch=1 all 0
rep(0, 9), rep(1, 9)) # out_ch=1: in_ch=0 all 0, in_ch=1 all 1
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 3L, 3L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 3L, 3L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 1.0)
y_zp <- make_scalar("y_zp", 0.0)
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp"), "Y",
attrs = list(
.onnx_attr_ints("kernel_shape", c(3L, 3L)),
.onnx_attr_string("auto_pad", "SAME_UPPER")))
graph <- .onnx_graph("test", list(qconv_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi, y_sc$vi, y_zp$vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
x <- rep(1, 32) # all ones, box filter
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
expect_equal(length(r), 32)
expect_true(all(is.finite(r)))
expect_true(all(r > 0))
})
# ── Boundary: QLinearConv all-zero input ───────────────────
test_that("chain qlinearconv: all-zero input (boundary)", {
inp <- .onnx_value_info("X", 1L, c(1L, 2L, 4L, 4L))
outp <- .onnx_value_info("Y", 1L, c(1L, 2L, 4L, 4L))
x_sc <- make_scalar("x_sc", 0.1)
x_zp <- make_scalar("x_zp", 0.0)
w_data <- c(1, 0, 0, 1)
w_raw <- unlist(lapply(w_data, .float_bytes))
w_t <- .onnx_tensor("W", c(2L, 2L, 1L, 1L), 1L, w_raw)
w_vi <- .onnx_value_info("W", 1L, c(2L, 2L, 1L, 1L))
w_sc <- make_scalar("w_sc", 1.0)
w_zp <- make_scalar("w_zp", 0.0)
y_sc <- make_scalar("y_sc", 1.0)
y_zp <- make_scalar("y_zp", 0.0)
qconv_node <- .onnx_node("QLinearConv",
c("X", "x_sc", "x_zp", "W", "w_sc", "w_zp", "y_sc", "y_zp"), "Y",
attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
graph <- .onnx_graph("test", list(qconv_node),
list(inp, x_sc$vi, x_zp$vi, w_vi, w_sc$vi, w_zp$vi, y_sc$vi, y_zp$vi),
list(outp),
list(x_sc$t, x_zp$t, w_t, w_sc$t, w_zp$t, y_sc$t, y_zp$t))
path <- tempfile(fileext = ".onnx")
writeBin(.onnx_model(graph), path)
x <- rep(0, 32)
result <- run_onnx(path, list(X = x))
r <- as.numeric(result)
expect_equal(r, rep(0, 32), tolerance = 1e-5)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.