Skip to content

feat(ai/sidecar): online SGD+EMA trainer with replay buffer (ADR-0781) - #178

Merged
lusoris merged 1 commit into
masterfrom
feat/sidecar-online-trainer
May 31, 2026
Merged

lusoris merged 1 commit into
masterfrom
feat/sidecar-online-trainer

Conversation

@lusoris

@lusoris lusoris commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implements Phase 4b item 4b.7 (Research-0733): Python sidecar container co-located with vmafx-node that continuously fine-tunes a tiny-AI ONNX model from live (features, true_score) pairs produced during scoring jobs.
  • Transport: Unix domain socket /tmp/vmafx-sidecar.sock (shared emptyDir), newline-delimited JSON — simpler and lower overhead than gRPC loopback for the expected < 100 pairs/s rate.
  • Replay buffer (10 000 samples, FIFO) prevents catastrophic forgetting during content-distribution shifts.
  • EMA shadow (beta=0.999, Mean Teacher) stabilises checkpoints against noisy burst updates.
  • Go FeedbackClient is non-blocking from the scoring path (1000-entry ring buffer + background drainer).

Deliverables checklist (ADR-0108)

  • Research digest: docs/research/0733-vmafx-sidecar-training-architecture.md (pre-existing, authored in Phase 4b planning)
  • Decision matrix: ADR-0781 ## Alternatives considered (gRPC vs Unix socket, dedicated training pool vs sidecar, Go-native SGD)
  • AGENTS.md invariant note: ai/sidecar/AGENTS.md (socket path sync, wire protocol, ONNX opset, buffer capacity)
  • Reproducer / smoke-test: pytest ai/sidecar/tests/ (replay buffer + SGD+EMA; PyTorch optional, skipped if absent)
  • Changelog fragment: changelog.d/added/sidecar-sgd-ema-online-trainer.md
  • Rebase notes: docs/rebase-notes.md §ADR-0781

Documentation (ADR-0100 / ADR-0042)

  • docs/ai/sidecar-online-training.md — operator guide covering quick-start, architecture diagram, all config env vars, checkpoint format, k8s 1.29+ native sidecar semantics, Go metrics, and v1 limitations.
  • docs/adr/0781-sidecar-sgd-ema-online-trainer.md — design rationale (ADR status: Proposed — architecture review needed before Accepted).

Architecture review questions

  1. node-deployment.yaml vs node.yaml — the new Helm chart layout on master added node.yaml; this PR still modifies node-deployment.yaml. Do the sidecar container stanzas need to move to node.yaml? Reviewer to advise.
  2. EMA decay 0.999 — appropriate for 50 encodes/hour workloads (~1000-step half-life ≈ 20 hours); may need tuning for lower-traffic deployments. Recommend operator override via VMAFX_SIDECAR_EMA_DECAY.
  3. Checkpoint hot-reload — v1 requires node restart to pick up new checkpoint. The VmafxModelTraining CRD + atomic session swap (Research-0733 §3.4) is a follow-up; confirm scope boundary.

Test plan

  • pytest ai/sidecar/tests/test_replay_buffer.py — no PyTorch needed, runs in < 1 s
  • pytest ai/sidecar/tests/test_sgd_ema.py — requires PyTorch; skipped if absent
  • go build ./cmd/vmafx-node/... — verifies online_feedback.go compiles clean
  • helm template . -f values.yaml --set sidecar.trainer.enabled=true — verify sidecar stanza renders

State / bug tracking

no state.md update needed: no bug opened, closed, or ruled not-affecting.

no ffmpeg-patches impact: no public C-API or header change.

🤖 Generated with Claude Code

@lusoris
lusoris force-pushed the feat/sidecar-online-trainer branch from 7fa9ed8 to f369e94 Compare May 29, 2026 12:12
Implements Phase 4b item 4b.7 (Research-0733): a Python sidecar container
co-located with vmafx-node that continuously fine-tunes a tiny-AI ONNX
model from live (features, true_score) pairs emitted during scoring jobs.

- ai/sidecar/replay_buffer.py: thread-safe bounded ring buffer (10 000
  samples, FIFO eviction) for experience-replay batching.
- ai/sidecar/sgd_ema.py: online SGD + EMA trainer (beta=0.999, gradient
  clipping, 100-step LR warmup, atomic ONNX export at opset 17).
- ai/sidecar/online_trainer.py: Unix-socket server (/tmp/vmafx-sidecar.sock),
  newline-delimited JSON, mixed-batch training, periodic checkpoint export
  with SHA-256 sidecar file.
- cmd/vmafx-node/online_feedback.go: non-blocking Go FeedbackClient with a
  1000-entry in-process ring buffer; fire-and-forget from the scoring path.
- deploy/helm/vmafx/templates/sidecar-trainer.yaml: named template
  vmafx.sidecarContainer, gated by sidecar.trainer.enabled (default false),
  k8s 1.29+ native sidecar (restartPolicy: Always).
- Tests: test_replay_buffer.py (thread-safety, eviction, stats),
  test_sgd_ema.py (EMA formula, warmup, ONNX export round-trip).
- ADR-0781, docs/ai/sidecar-online-training.md, changelog, rebase-notes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the feat/sidecar-online-trainer branch from f369e94 to fb60a88 Compare May 29, 2026 15:54
@lusoris
lusoris marked this pull request as ready for review May 31, 2026 13:38
@lusoris
lusoris merged commit ba4b5ac into master May 31, 2026
46 of 70 checks passed
@lusoris
lusoris deleted the feat/sidecar-online-trainer branch May 31, 2026 13:38
lusoris added a commit that referenced this pull request Jun 12, 2026
…CI) (#868)

* fix(codeql): resolve HIGH-severity security-cpp-high alerts (23 sites)

- cpp/integer-multiplication-cast-to-long (11): pre-cast one operand to
  size_t / double / ptrdiff_t before int*int multiplications in
  cambi.c, float_vif.c (log message), iqa/convolve.c (img_offset),
  moment.c, psnr.c, and vif_tools.c (four memcpy size expressions).
  Add stddef.h to convolve.c for ptrdiff_t.

- cpp/incomplete-parity-check (3): change `% 2 == 1` to `% 2 != 0`
  in vif_tools.c (assert), svm.cpp (powi loop), pdjson.c (JSON
  object key/value alternation). The == 1 form is wrong for negative
  operands; != 0 is always correct.

- cpp/wrong-type-format-argument (2): fix float_vif.c error log that
  printed size_t fields scaled_w/scaled_h with %d; change to %zu.

- cpp/world-writable-file-creation (1): in vmaf.cpp replace bare
  fopen("wb") with open(O_WRONLY|O_CREAT|O_TRUNC, 0644)+fdopen() on
  POSIX so the created file is never world-writable independent of the
  caller's umask. Add <fcntl.h>.

- cpp/path-injection (4): in test_output.c resolve the mkstemp-created
  path through realpath() immediately after creation, breaking the taint
  chain from getenv("TMPDIR") to the vmaf_write_output call site.

- cpp/toctou-race-condition (2): skipped — both sites are in test
  cleanup (RMDIR after stat assertion). The stat result drives a test
  assertion, not a security-sensitive access decision; no atomic
  replacement of open() is applicable to rmdir. Reported as skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(codeql): resolve security-python-and-ci CodeQL alerts

Fixes 18 open CodeQL alerts across the Python and CI categories:

- yaml.github-actions.security.run-shell-injection (#661): move
  github.event_name, github.base_ref, and github.event.before from
  inline ${{...}} interpolation to env: vars in the SYCL clang-tidy
  detect step of lint-and-format.yml.

- python.lang.security.use-defused-xml-parse (#216, #217): replace
  xml.etree.ElementTree with defusedxml.ElementTree in
  feature_extractor.py and quality_runner.py; add defusedxml>=0.7.1
  to python/pyproject.toml and python/requirements.txt.

- py/undefined-export (#352, #353, #354, #616): restructure
  aiutils/__init__.py to do a conditional eager import of the parquet
  helpers so the names are defined when pyarrow is present, and only
  include them in __all__ when the import succeeded.

- py/stack-trace-exposure (#178, #179, #585): log exception detail
  server-side and return a generic message to the HTTP client in
  http_transport.py _handle_score (invalid JSON, bad params, scorer
  error branches).

- python.lang.security.audit.dangerous-subprocess-use-tainted-env-args
  (#227, #372): add shlex.quote() around user-supplied path arguments
  passed into shell strings in extract_ugc_features.py and
  test_bbb_e2e_v5_bug_cluster.py.

- py/file-not-closed (#677, #678): replace bare open() calls with
  context managers in test_coverage_round3.py.

- py/redundant-comparison (#427, #431): remove redundant
  assert not (x != y) lines that duplicate the preceding assert x == y.

- py/equals-hash-mismatch (#182): convert RdPoint to frozen=True
  dataclass so __eq__ and __hash__ are generated consistently.

- py/inheritance/signature-mismatch (#197): add result_dict=None
  default to EnsembleVmafQualityRunner._populate_result_dict so the
  signature is compatible with the base class.

- py/multiple-definition (#201): drop redundant assignment to
  feature_found in feature_extractor.py wildcard discovery path.

- py/str-format/surplus-named-argument (#204): remove unused
  dataset= kwarg from the format() call in routine.py.

Skipped: python.lang.security.audit.insecure-file-permissions (#373) —
  the Unix socket at 0o660 is intentional (Go sidecar node must write
  to it and runs as the same UNIX group); tightening to 0o644 would
  break the IPC channel.

Skipped: py/path-injection (#180, #181) — _validate_path() already
  resolves the path and checks it against an allowlist before any file
  operation; the data flow is secure and the CodeQL dataflow trace is a
  false positive on this allowlisted pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@lusoris lusoris added this to the 1.0.0 — First release milestone Sep 4, 2026
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.

1 participant