Skip to main content

HomOps Reference

HomConv

Run a 2D convolution on packed encrypted feature maps.

CvML
RunsServer
Changes shapeYes - channels: C_inC_out; spatial length stays H*W
Changes scaleYes - packed kernel multiply
Levels spentTypically 1 - packed mul with mod-switch
RotatesYes - kernel tap alignments
KeysRotation key

What it does

HomConv is a first-class 2D convolution (OP_TYPE.Conv). It is not an Unfold → Linear → Reshape composite. The backend aligns kernel taps with HomRotateSum, multiplies by packed plaintext kernel masks, sums over rotations and in-channels, optionally adds bias, and applies a stride gate when stride != (1, 1).
Reach for it when you need encrypted Conv2d-style layers on square spatial maps, or grouped / depthwise conv via groups (see HomAvgPool). For a fused conv + batch-norm path, see HomConvBnFused.

Signature

HomConv(
    in_channels,                        # required
    out_channels,                       # required
    kernel_size,                        # int | (h, w)
    stride,                             # int | (h, w)
    padding,                            # int | (h, w) | None → (kH, kW)
    groups=1,
    bias=False,
    dilation=(1, 1),
    image_hw=None,
)
Kernel weights (and bias when enabled) come in through set_data. The stored kernel shape is (out_channels, in_channels // groups, kH, kW).

Parameters

ParameterTypeDefaultDescription
in_channelsintrequiredInput channels C_in.
out_channelsintrequiredOutput channels C_out.
kernel_sizeint | (h, w)requiredNormalized to (kH, kW).
strideint | (h, w)requiredNormalized to a 2-tuple. (1, 1) takes the non-strided path.
paddingint | (h, w) | Nonerequired argIf None, defaults to (kH, kW). Otherwise normalized to a 2-tuple.
groupsint1Grouped convolution. Must be greater than 0 and divide both channel counts.
biasboolFalseIf True, set_data expects weight plus a 1D bias of length C_out.
dilationint | (h, w)(1, 1)Spatial dilation of the kernel, as a scalar or (dh, dw).
image_hw(h, w) | NoneNoneOptional input image spatial size hint used when the packed layout needs an explicit (H, W).
Rules that depend on the value
SettingRuleIf you break it
groupsMust be > 0 and divide both in_channels and out_channels.ValueError.
set_data (bias=False)set_data(weight) with weight.shape == kernel_shape.Shape check fails.
set_data (bias=True)set_data(weight, bias); bias is 1D with length C_out.Shape check fails.

Requirements

  • set_data called. Weights must be attached before prep (AvgPool sets them in the constructor).
  • Rotation key entries. Every non-identity kernel tap rotation needs an entry in the evaluation key. The compiler derives them; you don't list them yourself.
  • Square packing. Feature maps are packed as (C, H*W) with H == W. Ring dimension must satisfy internal_n ≥ H*W.
  • Identity tap. Padding must keep the identity kernel tap present: 0 ≤ pad_h < kH and 0 ≤ pad_w < kW.
Typical packing: reshape (C, H, W)(C, H*W) before encrypt (see HomReshape), with n_axis on the spatial slots axis. These surface as compilation errors; see Compilation errors.

Shape effect - yes

Rule: deploy infers tensor_shape = (C_out, H*W) with n_axis = -1. Spatial length H*W is preserved in metadata; strided conv zeros inactive slots via a stride gate (values live on a coarser grid inside the same length).
Stagept_shape
client_pre HomReshape((C_in, H*W))(C_in, H*W)
After HomConv(C_out, H*W)

Scale effect - yes

The packed kernel multiply updates scale the same way a ConstMul / vector packing multiply does. Bias add does not multiply scale. How scale moves through a pipeline is on Ciphertext state - Scale.

Level budget

StepLevels spent
RotateSum (stack)0
Packed kernel mulTypically 1 (deploy models mod-switch)
AxisModSum0
Bias add0
How primes are organized in the chain is explained on Level budget - The chain.

Keys

Rotation key. Needed for kernel tap alignments. No square key: the encrypted operand is multiplied only by plaintexts.

Example

A biased 3×3 conv on a 32×32 RGB map, packed then unpacked around the encrypted step:
pipeline.pyPYTHON
from lattica_build.operators import HomConv, HomReshape
from lattica_build.base_classes.hom_pipeline import HomomorphicPipeline
import torch

C_in, C_out, H = 3, 8, 32
conv = HomConv(
    in_channels=C_in,
    out_channels=C_out,
    kernel_size=3,
    stride=1,
    padding=1,
    groups=1,
    bias=True,
)
conv.set_data(
    torch.randn(C_out, C_in, 3, 3),
    torch.randn(C_out),
)

pipeline = HomomorphicPipeline(
    client_pre=[HomReshape((C_in, H * H))],
    hom=conv,
    client_post=[HomReshape((C_out, H, H))],
    input_shape=(C_in, H, H),
    n_axis=1,
)

See also