Phase 3 of ADR-103. Cross-compiled aarch64 + x86_64 on ruvultra, signed
with COGNITUM_OWNER_SIGNING_KEY (Ed25519), uploaded to GCS, and live-
installed on the cognitum-v0 Pi 5 alongside cog-pose-estimation.
Real-hardware bench on cognitum-v0:
./cog-person-count-arm health
→ backend=candle-cpu, count=0, confidence=0.49, p95=[0,7]
30 sequential health invocations: 0.276 s → 9.2 ms/invocation cold
Compares to cog-pose-estimation's 8.4 ms — count cog is ~10% slower
because the dual-head (count softmax + confidence sigmoid) does ~2x
the work after the shared encoder.
GCS release artifacts (publicly downloadable, SHA-verified):
arm/cog-person-count-arm 2,168,816 B
sha: 36bc0bb0...0d47b507b3c3
sig: R/00xdzHriyr/2r...JK+a6k71NDg== (Ed25519)
x86_64/cog-person-count-x86_64 2,615,528 B
sha: 76cdd1ec...3923 7392b01db
sig: QB+8cnGSMQmu...ZtTNIQ2rDg== (Ed25519)
arm/cog-person-count-count_v1.safetensors 392,088 B
sha: dacb0551...e6e04ff56d15c3a65a9ff
Live install at /var/lib/cognitum/apps/person-count/ on cognitum-v0
matches the layout of every other installed cog (anomaly-detect,
seizure-detect, pose-estimation): cog-person-count-arm binary,
count_v1.safetensors weights, manifest.json, config.json.
Adds:
* v2/.../cog/artifacts/manifests/{arm,x86_64}/manifest.json — full
ADR-100 schema with all fields filled (sha + sig + size + URL +
build_metadata carrying the v0.0.1 honest training caveats).
* docs/benchmarks/person-count-cog.md — appends "Live appliance
install" and "Signed GCS release artifacts" sections to the
benchmark log.
Honest v0.0.1 caveat still applies (class-1 accuracy 0% on the held-
out tail of the single-session training data) — same data-bound
limit as pose_v1. The shipped artifact is the *vehicle*; production-
quality accuracy follows from multi-room paired data per ADR-103's
v0.2.0 plan + #645.
|
||
|---|---|---|
| .. | ||
| artifacts | ||
| README.md | ||
| config.schema.json | ||
| manifest.template.json | ||
README.md
Person Count Cog
Learned multi-person counter for WiFi CSI — designed in ADR-103, packaged per ADR-100, discoverable through ADR-102.
What it does
Replaces the PR #491 slot heuristic (subcarrier_diversity / dedup_factor) with a Candle network that emits a calibrated count distribution + confidence per CSI window. Multi-node deployments fuse N per-node predictions through a confidence-weighted log-sum (Bayesian product of experts), optionally bounded above by a Stoer-Wagner min-cut from the subcarrier-similarity graph.
Output (per frame)
{
"ts": 1779210883.444,
"level": "info",
"event": "person.count",
"fields": {
"tick": 12345,
"count": 2,
"confidence": 0.81,
"count_p95_low": 1,
"count_p95_high": 3,
"n_nodes": 3,
"probs": [0.01, 0.03, 0.81, 0.13, 0.01, 0.005, 0.003, 0.002]
}
}
Downstream consumers can render the most-likely count when confidence is high, or fall back to a [lo, hi] band with a "?" badge when the model is uncertain — that's how this Cog closes the loop on #499's ghost-skeleton UX.
Status — v0.0.1
| Component | State |
|---|---|
| Crate compiles, library API stable | ✅ |
| Tests pass (15 total: 8 smoke + 7 fusion) | ✅ |
Four-verb runtime contract (version, manifest, health) |
✅ |
Trained count_v1.safetensors artifact |
✅ shipped at cog/artifacts/count_v1.safetensors (392 KB) |
| ONNX export | ✅ count_v1.onnx (16 KB), bit-compatible architecture |
| Honest accuracy reporting | ✅ See docs/benchmarks/person-count-cog.md — 65.1% eval acc on a single-session dataset; confidence head Spearman 0.023 ⇒ uncalibrated for v0.0.1 |
run subcommand (long-running loop) |
⏳ same shape as cog-pose-estimation::runtime, lands in follow-up |
| Signed binary on GCS | ⏳ release pipeline |
| Stoer-Wagner min-cut clip in fusion stage | ⏳ v0.2.0 (hook in fusion::fuse_with_mincut_clip is stubbed) |
Honest v0.0.1 caveat
count_v1 was trained on a single 30-minute solo recording. The model overfit by epoch ~100 and the "best" checkpoint is one that effectively predicts the eval-window class distribution (mostly class-0). Class-1 accuracy on the held-out tail = 0%. This v0.0.1 is a working pipeline with a degenerate model, not a usable counter yet — same data-bound failure mode as pose_v1 (#645), same fix: multi-room paired recordings.
cog-person-count health will load the real safetensors and report backend: candle-cpu rather than backend: stub, so the cog-gateway can verify the model loaded — but operators should treat the v0.0.1 count outputs as scaffold-validation rather than production data. The 2.36 MB binary + 392 KB weights + 16 KB ONNX are all real and reusable as soon as more data lands.
Security
The cog has a very small attack surface — by design, it's a pure consumer of CSI data, not a server:
| Threat | Mitigation |
|---|---|
| Untrusted model file mmap | count_v1.safetensors is loaded via VarBuilder::from_mmaped_safetensors (unsafe block, documented). The release pipeline signs the file with COGNITUM_OWNER_SIGNING_KEY per ADR-100; the appliance's cog-gateway verifies the Ed25519 signature against weights_sha256 before placing the file under /var/lib/cognitum/apps/person-count/. |
| Non-finite outputs from a corrupted model | CountPrediction::is_finite() is checked in cmd_health and in the v0.0.1 run-loop before any person.count event is emitted; non-finite outputs fail-closed. |
| Sensing-server fetch failures | When the sensing source goes away the cog emits a WARN event and skips the frame — same fail-open-as-log pattern as cog-pose-estimation. No crash, no leaked file descriptors, no stuck pid file. |
| Fusion divide-by-zero / log-of-zero | fuse_confidence_weighted floors confidences at 1e-3 and floors probabilities at 1e-9 before taking logs. Empty input returns the stub default rather than NaN-propagating. |
| Over-the-cap mass after min-cut clip | fuse_with_mincut_clip re-normalises the surviving prefix; if all mass was above the cap (degenerate case), it places mass at the cap class rather than producing a zero distribution. |
| Output spoofing via stdout | Events go to stdout exactly as ADR-100's runtime contract specifies — the cog-gateway parses each line as JSON. No interactive prompts, no shell escapes, no ANSI control sequences from this cog. |
The cog opens zero network listeners and writes to zero files under /var/lib/cognitum/apps/person-count/ beyond the standard pid, output.log, and error.log that the cog-gateway manages externally.
Performance / optimization
Release build: 2.36 MB stripped binary on x86_64-unknown-linux-gnu (smaller than cog-pose-estimation's 4.5 MB because we don't transitively pull wifi-densepose-train).
Workspace release profile already enables opt-level = 3, lto = "fat", codegen-units = 1, strip = true. No further per-cog optimization knobs needed.
Cold-start latency (30 sequential health invocations, Windows x86_64, candle-cpu backend):
| Cog | Cold-start |
|---|---|
cog-pose-estimation |
76.2 ms |
cog-person-count |
53.3 ms |
Long-running run warm inference: sub-millisecond per frame in the stub backend (single softmax over 8 classes is essentially free). The trained-model warm path is bounded by the three Conv1d layers — projected ≤ 2 ms on a Pi 5 once count_v1.safetensors lands, well under the ≤ 5 ms ADR-103 budget.
See also
- ADR-103 — Design, SOTA comparison, acceptance gates.
- ADR-100 — Cog packaging spec.
- PR #491 — The heuristic this Cog replaces.
- Issue #499 — Original "double skeletons" report that motivated ADR-103.