@trillboards/edge-federated
v1.0.1
Published
On-device federated learning, gradient upload, and VAS attestation for Trillboards Edge AI SDK
Maintainers
Readme
@trillboards/edge-federated
Privacy-preserving federated learning for DOOH devices. Train models on-device from audience and contextual signals, upload only sparse gradients. No raw data leaves the device.
Install
npm install @trillboards/edge-federatedWhat This Does
Enables on-device model training that improves audience prediction accuracy over time while preserving viewer privacy:
- Federated Trainer — accumulates training samples from audience sensing, computes gradients locally, uploads only the top 10% sparsest gradients every 6 hours
- Federated Model Client — manages local model versions, checks the cloud for global model updates, handles serialization, version reconciliation, and optional inline-weights manifests for small models (e.g. 64-d taste vectors)
- Slice Context — training is partitioned by venue type, daypart, geography, and device profile for fine-grained model personalization
Usage
import { FederatedTrainer, FederatedModelClient } from '@trillboards/edge-federated';
// Initialize trainer (Ed25519 keys persisted to ~/.trillboards/keys.json)
const trainer = new FederatedTrainer(machineId, {
persistenceDir: '/data/trillboards',
});
// Accumulate gradients from on-device training loops
trainer.accumulate('attention', gradientFloat32Array, {
venueType: 'retail',
venueSubtype: 'mall',
daypart: 'afternoon',
geo: 'us-east',
deviceProfile: 'tier_3',
});
// Start the upload timer — fires every 6 hours and flushes eligible models
trainer.start(async (payload) => {
// payload is a GradientUploadPayload — flat slice context, gradients +
// gradientIndices, fingerprint, localLoss. Matches the server wire format.
await fetch('https://api.trillboards.com/v1/federated/gradients', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
});
// Check for global model updates (sister API surface)
const client = new FederatedModelClient(
'https://api.trillboards.com',
deviceFingerprint,
);
const update = await client.checkForUpdate('attention');
if (update?.updateAvailable) {
await client.downloadModel(update.model);
}How It Works
- On-device training — model trains on local audience data (face count, attention, emotion, dwell time)
- Sparse gradient extraction — only the top 10% most significant gradients are selected (top-K sparsification)
- Gradient upload — compressed gradients sent to cloud every 6 hours (configurable)
- Global aggregation — cloud aggregates gradients from all devices to update the global model per slice
- Model distribution — updated global model pushed back to devices; small models can ship inline weights in the OTA manifest to skip a second HTTP round trip
Raw audience data never leaves the device. Only mathematical gradient values are transmitted.
Configuration
| Parameter | Default | Description |
|-----------|---------|-------------|
| UPLOAD_INTERVAL_MS | 21,600,000 (6 h) | Gradient upload cadence |
| TOP_K_RATIO | 0.1 (10%) | Fraction of gradients to upload by absolute magnitude |
| MIN_SAMPLES_FOR_UPLOAD | 100 | Minimum accumulated samples before upload is eligible |
| MAX_GRADIENT_BUFFER_SIZE | 10,000 | Max buffered gradient entries per model (FIFO eviction) |
Constants source of truth
These values are mirrored from @trillboards/iab-taxonomy/src/fein.ts (the
sister PR fein-p0b-constants adds them as the canonical source). Once that
package ships, a follow-on PR will swap the hard-coded constants in
FederatedTrainer.ts for import from @trillboards/iab-taxonomy. Until
then, the values must be kept in lockstep across:
trillboards-edge-sdk/packages/edge-federated/src/FederatedTrainer.tstrillboard-ctv/agent-core/.../FederatedTrainer.kt(Kotlin canonical)- this README
Wire Format (v1.0.0)
GradientUploadPayload (exported from @trillboards/edge-core) emits the same
shape Kotlin emits to POST /v1/federated/gradients:
{
screenId: string,
modelType: string,
venueType: string,
venueSubtype?: string,
daypart?: string,
geo?: string,
deviceProfile?: string,
gradients: number[],
gradientIndices: number[],
sampleCount: number,
localLoss: number,
modelVersion: number,
fingerprint: string,
publicKeyId: string | null,
signature: string | null,
timestamp: number,
}The v0.2.x wire format (sliceContext nested, sparseValues / sparseIndices,
deviceFingerprint) was silently rejected by the production route. v1.0.0 is
the first release that actually integrates with the server.
License
MIT
