Skip to content

spec(conv2d_layer): forward slides its kernel; scalar conv_at for the nn-conv widget (Closes #10612) - #10744

Merged
gHashTag merged 1 commit into
masterfrom
fix/conv2d-slide
Oct 19, 2026
Merged

gHashTag merged 1 commit into
masterfrom
fix/conv2d-slide

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #10612. Part of EPIC #9063 (the course factory), course 10 Neural networks in t27.

Why

specs/ml/layers/conv2d_layer.t27 did not slide a kernel. forward multiplied one kernel-sized patch elementwise by the weights and added the bias. On the 2x2 input with the 2x2 kernel, a convolution gives one number, 11 + 20.5 + 30.25 + 40.1 + 0.1 = 3.25. The spec gave four, and its test checked those four. None of its fns lowered to WASM either. Because of that, course 10 lesson 10 on t27.ai (trinity apps/website/public/widgets/nn-conv/) uses average pooling (pool4At from avgpool2d_layer.t27) instead of a convolution until this lands.

What changed (one spec, its two seals, one NOW entry)

  • forward is now the deep-learning convolution: a 2-D cross-correlation, so the kernel is not flipped. It runs over one H x W channel, row-major, with a stride and zero padding. The output side is (n + 2p - k) / s + 1, and the bias is added once per output cell. It takes height, width. The per-cell sum lives in conv_cell, which forward and conv_at share.
  • backward is the sliding gradient: grad_input and grad_weights are accumulated over every window. The bias gradient is now its return value instead of a 1-element slice. That keeps it at 8 parameters, so the site compiler no longer prints its "9 parameters" warning.
  • Scalar entry points take only u8, u32 and f32, so 999 scripts/t27-logic.mjs lowers them all. It reports 5 fns lowered: outSize, convAt, kernelAt, imageAt, defaultAt.
    • out_size(n, kernel, stride, padding) returns 0 when the kernel does not fit or when the stride or the kernel is 0.
    • conv_at(kernel_id, stride, padding, index) convolves the fixed 5x5 image (a bright 3x3 square) with kernel 0 identity, 1 Sobel x, 2 Sobel y or 3 box blur. It returns 0.0 past the end.
    • kernel_at(kernel_id, index) and image_at(index) return the same numbers the spec convolves, so the widget can draw them.
    • default_at(index) is the old 2x2 case. It is one cell, 3.25.
  • VERSION 2, with a dated header note and a runtime test that VERSION is 2.
  • Three unused default_* helpers had one identical body (a t27c dupes --bodies group). They are removed.

Evidence

The t27c bootstrap has not changed since 239b4b0. Zig is 0.16.0.

t27c test-report --verbose specs/ml/layers/conv2d_layer.t27: 14 tests, 14 pass, FAIL 0, 0 vacuous, and 5 invariants proved.

test runtime asserts
forward_basic_case (2x2 -> 3.25) 5
forward_slides_over_3x3 (3.6, 5.45, 9.15, 11.0) 8
backward_basic_case 7
backward_slides_over_3x3 16
backward_strided 14
backward_padded 14
out_size_formula 1
image_and_kernels_are_the_fixtures 1
conv_sobel_x_valid (table row 1) 10
conv_sobel_y_valid (row 2) 9
conv_box_blur_valid (row 3) 18
conv_identity_padded_is_the_image (row 4) 28
conv_sobel_x_strided_padded (row 5) 9
version_is_the_header_note 1

Negative controls. Each mutant was applied, test-report was run, and the spec was restored. All 9 were killed:

mutant red tests
elementwise fallback (the v1 body) 3
stride ignored 1
padding ignored 3
kernel flipped (true convolution) 6
bias added k*k times 3
backward: padding ignored 1
backward: kernel flipped 3
backward: has_bias ignored 1
backward: stride ignored 1

Lowering. I ran node scripts/t27-logic.mjs <t27> ml/layers/conv2d_layer and then ran the compiled WASM in node:

  • defaultAt(0) = 3.25
  • outSize(5,3,1,0) = 3 and outSize(5,3,2,1) = 3
  • convAt(1,1,0,3) = 4, convAt(1,1,0,5) = -4 and convAt(1,2,1,3) = 4
  • All five rows of the issue's table come back exactly, and identity with padding 1 returns the image.

Site compiler (trinity public/t27/t27_compiler.wasm): typecheck ok, 0 errors, 0 discarded, HIR ok, and all seven backends print (c, js, rust, ts, verilog, verilog_hir, zig).

Local equivalents of the required checks:

  • own-language: check_all and check_budget both exit 0.
  • parse ratchet: check_specs_still_parse.py reports 0 newly unparseable.
  • duplicate bodies: dupe_scan.py reports no new duplicate body and no known group grew. t27c dupes --bodies names no group in this spec.
  • validate: check_json_parses.py OK.
  • NOW entry: check_now_entry_shape.py OK.

No spec in specs/ uses Conv2d, so there are no consumers to re-run. t27c seal --save refreshed both layers_Conv2d.json and Conv2d.json, which name the same spec.

Not in this PR: re-vendoring the spec into trinity and switching nn-conv/tool.js from pool4At to convAt.


Generated by Claude Code

… nn-conv widget (Closes #10612)

forward multiplied one kernel-sized patch elementwise by the weights, so
the 2x2 input with the 2x2 kernel gave four numbers where a convolution
gives one (3.25), and its test checked the four. No fn lowered to WASM,
so course 10 lesson 10 on t27.ai (trinity widgets/nn-conv) falls back
to average pooling.

- forward: 2-D cross-correlation over one H x W channel with stride and
  zero padding, side (n + 2p - k) / s + 1, bias once per output cell.
  backward: the sliding gradient; the bias gradient is its return value.
- Scalar fns, lowered by 999 scripts/t27-logic.mjs (5 fns): out_size,
  conv_at (fixed 5x5 square image, identity / Sobel x / Sobel y / box
  blur), kernel_at, image_at, default_at (the 2x2 case, 3.25 in node).
- VERSION 2 with a dated header note, and a test that pins it.
- test-report 14 of 14 pass, 0 vacuous; every row of the issue's table
  has a test; a 3x3 input with the 2x2 kernel pins the slide.
- 9 negative-control mutants (fallback, stride, padding, flip, bias, and
  four in backward), 9 killed.
- Site compiler: typecheck ok, all seven backends. dupes --bodies: no
  group in this spec (three unused duplicate helpers removed).
- Seals saved; NOW entry in docs/now/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 01:03:08 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 45
PRs with All Checks Green 5
READY 4
FAILING 45
PENDING 0
NO CHECKS YET 0

These columns do not partition: 4 + 45 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag enabled auto-merge (squash) October 19, 2026 01:03
@gHashTag
gHashTag merged commit 2ee0cb6 into master Oct 19, 2026
33 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spec: conv2d_layer.t27 slides its kernel -- the convolution lesson of course 10 (Neural networks in t27)

2 participants