---
title: "ClikaRT::nn::QConv"
sidebar_label: "QConv"
description: "Statically-quantized convolution module: quantized weight AND activation-quantized compute where the backend serves the scheme, Conv's static-quant sibling (construct with a QTensor weight)."
---

<!-- Generated by tools/doxygen2mdx from Doxygen XML. Do not edit. -->

<span class="api-kind api-kind--class">class</span>

**Header:** `ClikaRT/nn/qconv.h`  
**Inherits:** [`ClikaRT::nn::Module`](./Module.md)

A bound, weight-packing static-quant (QDQ) convolution, the public face of the runtime's quantized conv, exposed as an `nn::Module` leaf. BOTH operands are quantized: the weight binds once as a `QTensor` (codes + scheme) and packs into the backend's integer kernel layout on the first forward; the activation arrives ALREADY quantized (from calibration or a preceding `quantize`; this module never quantizes it). For the weight-only flavor (float activations against a quantized weight) use `QConvWoQ` instead.

The lifecycle (uniform across every weight-bearing `nn` module):

1. `make(in, out, kernel, options, ...)`, the ONE constructor: declares storage-free `weight` / `bias` slots under their canonical names.
2. `set_weights(qw[, b])` or `load_state_dict(...)` binds; a quantized payload lands as-is (its scheme travels with it). Optionally `set_output_quantization(...)` binds the output requant params.
3. `forward(x)` packs ON FIRST CALL (once, thread-safe), then serves the packed form every step; `LoadOptions::pack_on_load` pays the pack at load time.

Output dtype is conditional on the bound output quantization:

- `set_output_quantization` CALLED: the conv product requantizes to the bound scheme and `forward` returns a QUANTIZED tensor carrying it.
- NOT called: the dequant path; `forward` returns a FLOAT tensor at the activation scheme's float target dtype.

Layout: activations are channels-last `[N, D1..Dn, C]`; the weight's logical shape is OHWI `[out_channels, K1..Kn, in_channels/groups]` (the quantized codes' element geometry).

The bias follows the op's EXIT domain, one law for `QConv` and `QLinear` on every backend: the conv product is an integer accumulator at `a_scale·w_scale[o]` per output channel. An INTEGER bias is a quantized value on that grid and adds in the accumulator as is (the output reads it as `b[o]·a_scale·w_scale[o]`); a FLOAT bias is a real value and adds at the exit as is (every `QConv` exit forms a real value: the dequant path and the requant path's epilogue). The bias binds at its OWN dtype, pin or no pin: its dtype is what says which of the two it is. Held via `std::shared_ptr` (an `nn::Module` leaf); copy/move are pinned by the base.

Statically-quantized convolution module: quantized weight AND activation-quantized compute where the backend serves the scheme, [`Conv`](./Conv.md)'s static-quant sibling (construct with a [`QTensor`](../QTensor.md) weight).

Layouts follow [`Conv`](./Conv.md) (channels-last, OHWI weight). The module owns its weights: construct with `make(...)`, or declare shapes and bind a checkpoint via [`load_state_dict`](./Module.md#load_state_dict). The first forward packs the weight (or [`LoadOptions::pack_on_load`](./LoadOptions.md#pack_on_load) packs it at the end of the load); the pack is the one resident copy, and [`named_parameters()`](./Module.md#named_parameters) / [`state_dict()`](./Module.md#state_dict) read the weight back from it; `to(dtype)` restores, casts, and re-packs on the next forward.

## Static member functions

### make() {#make}

<div class="api-signature">

`static` `std::shared_ptr<QConv>` **`make`**(<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::int64_t` `in_channels`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::int64_t` `out_channels`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Span`](../Span.md)`<const std::int64_t>` `kernel`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Span`](../Span.md)`<const std::int64_t>` `stride` `=` `{}`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Span`](../Span.md)`<const std::int64_t>` `padding` `=` `{}`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Span`](../Span.md)`<const std::int64_t>` `dilation` `=` `{}`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::int64_t` `groups` `=` `1`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`bool` `bias` `=` `false`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`ops::PadMode`](../ops/index.md#PadMode) `mode` `=` [`ops::PadMode::Constant`](../ops/index.md#PadMode),<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::optional<double>` `value` `=` `std::nullopt`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::optional<`[`ops::Activation`](../ops/index.md#Activation)`>` `activation` `=` `std::nullopt`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`DataType`](../../index.md#DataType) `dtype` `=` [`DataType::Undefined`](../../index.md#DataType),<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Device`](../Device.md) `device` `=` [`Device::cpu`](../Device.md#cpu)`()`<br />
)

</div>

Defaults: `stride` / `padding` / `dilation` = empty (unit stride, no pad, unit dilation), `groups` = `1`, `bias` = `false` (no bias slot), `mode` = `PadMode::Constant`, `value` = unset (zero fill), `activation` = none, [`dtype`](../dtype/index.md) = `Undefined` (adopt), [`device`](../device-namespace/index.md) = CPU.

**Throws**

- `ClikaRT::Error`: as stated above.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 95

</div>

## Member functions

### \~QConv() {#QConv}

<div class="api-signature">

**`~QConv`**() `override`

</div>

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 109

</div>

### set\_weights() {#set_weights}

<div class="api-signature">

`void` **`set_weights`**([`QTensor`](../QTensor.md) `weight`, [`OptionalTensor`](../../index.md#OptionalTensor) `bias` `=` `{}`)

</div>

Bind the declared slots positionally: `weight` (a quantized [`QTensor`](../QTensor.md) whose logical shape is OHWI `[out, K.., in/groups]`) and, when the module was made with one, `bias` (`[out]`, bound at its own dtype: an integer bias is a quantized value that adds in the integer accumulator at `a_scale·w_scale`; a float bias is a real value that adds at the exit, the file doc's law). The quantized payload lands as-is (no dtype cast; quant geometry is scheme-defined; the logical dims are checked). Re-binding after a pack drops the pack. Raises [`ClikaRT::Error`](../Error.md) on a geometry mismatch or a bias without a declared bias slot.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 122

</div>

### set\_output\_quantization() {#set_output_quantization}

<div class="api-signature">

`void` **`set_output_quantization`**(<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`Tensor`](../Tensor.md) `scale`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;[`OptionalTensor`](../../index.md#OptionalTensor) `zero_point` `=` `{}`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::int64_t` `quant_axis` `=` `-1`,<br />
&nbsp;&nbsp;&nbsp;&nbsp;`std::optional<`[`DataType`](../../index.md#DataType)`>` `out_dtype` `=` `std::nullopt`<br />
)

</div>

Optionally bind the OUTPUT quantization: the conv product requantizes to `scale` / `zero_point` (per-tensor affine) and [`forward`](./QConv.md#forward) returns a quantized tensor whose code dtype is `out_dtype` (defaulting to the zero-point's dtype when one is bound, else Int8). Calling it again re-binds (and drops any pack); never calling it keeps the float (dequant) output path. Raises [`ClikaRT::Error`](../Error.md) on undefined `scale`.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 135

</div>

### to\_impl(StreamOrDevice) {#to_impl}

<div class="api-signature">

`virtual` [`Result`](../Result.md)`<void>` **`to_impl`**([`StreamOrDevice`](../StreamOrDevice.md) `where`) `override`

</div>

Placement move: rebuilds the pack on the target. A dtype cast is Unsupported; the weight stays quantized at rest, and dequantize explicitly if a dense copy is wanted. [`can_cast_to_impl`](./QConv.md#can_cast_to_impl) reports the same refusal without moving a byte (the tree-wide check behind `Module::to(dtype)`).

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 147

</div>

### to\_impl(DataType) {#to_impl-2}

<div class="api-signature">

`virtual` [`Result`](../Result.md)`<void>` **`to_impl`**([`DataType`](../../index.md#DataType) `dtype`) `override`

</div>

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 148

</div>

### can\_cast\_to\_impl() {#can_cast_to_impl}

<div class="api-signature">

`virtual` [`Result`](../Result.md)`<void>` **`can_cast_to_impl`**([`DataType`](../../index.md#DataType) `dtype`) `const override`

</div>

Whether THIS module's own slots can be cast to [`dtype`](../dtype/index.md) (children are asked by the tree walk, never here). A failure is the refusal `to(dtype)` would raise; `Module::to(dtype)` asks every module of the tree BEFORE it casts any, so a refused cast leaves the tree untouched and names the leaf. Default: a dense module casts. Override in a leaf that pins its dtype.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 149

</div>

### forward() {#forward}

<div class="api-signature">

[`Tensor`](../Tensor.md) **`forward`**([`QTensor`](../QTensor.md) `x`) `const`

</div>

Apply the bound quantized weight to the QUANTIZED activation: `x` `[N, D1..Dn, in]` (a [`QTensor`](../QTensor.md), quantized by calibration or a preceding `quantize`) -\> `[N, O1..On, out]` (float, or quantized when [`set_output_quantization`](./QConv.md#set_output_quantization) bound a scheme). The first call packs (once, thread-safe); every later call reuses the pack. Raises [`ClikaRT::Error`](../Error.md) on a shape mismatch or a still-fake slot.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 158

</div>

## Protected member functions

### initialize\_impl() {#initialize_impl}

<div class="api-signature">

`virtual` [`Result`](../Result.md)`<void>` **`initialize_impl`**() `override`

</div>

The pack hook ([`Module::initialize_impl`](./Module.md#initialize_impl)): packs now (idempotent, thread-safe). Every declared slot must hold a real (loaded) tensor; a still-fake slot is a clean error. After the pack the `weight` slot reads back from the pack, the single resident copy of the codes; the bias and the requant params stay bound.

<div class="api-declared">

Declared in `ClikaRT/nn/qconv.h`, line 171

</div>
