@trillboards/edge-sdk 0.2.61 → 0.2.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/deploy/litertlm/test_trillboards_litertlm_shim.py +132 -0
  2. package/deploy/litertlm/trillboards-litertlm-shim.pth +1 -0
  3. package/deploy/litertlm/trillboards_litertlm_shim.py +214 -0
  4. package/deploy/macos/bench-local-vlm.sh +8 -4
  5. package/deploy/macos/build-macos-pkg.sh +4 -0
  6. package/deploy/macos/scripts/setup-litertlm.sh +99 -2
  7. package/deploy/systemd/setup-litertlm.sh +99 -2
  8. package/deploy/windows/build-windows.ps1 +4 -0
  9. package/deploy/windows/register-litertlm-task.ps1 +22 -0
  10. package/deploy/windows/setup-litertlm.ps1 +131 -3
  11. package/deploy/windows/trillboards-edge.cmd +19 -0
  12. package/dist/CloudSensingManager.d.ts +65 -0
  13. package/dist/CloudSensingManager.d.ts.map +1 -1
  14. package/dist/CloudSensingManager.js +65 -30
  15. package/dist/CloudSensingManager.js.map +1 -1
  16. package/dist/DurableVlmCandidateSpool.d.ts +21 -0
  17. package/dist/DurableVlmCandidateSpool.d.ts.map +1 -1
  18. package/dist/DurableVlmCandidateSpool.js +126 -1
  19. package/dist/DurableVlmCandidateSpool.js.map +1 -1
  20. package/dist/EdgeAgent.js +1 -1
  21. package/dist/LiteRtVlmProvider.d.ts +74 -7
  22. package/dist/LiteRtVlmProvider.d.ts.map +1 -1
  23. package/dist/LiteRtVlmProvider.js.map +1 -1
  24. package/dist/MosaicSource.d.ts +18 -0
  25. package/dist/MosaicSource.d.ts.map +1 -1
  26. package/dist/MosaicSource.js +23 -0
  27. package/dist/MosaicSource.js.map +1 -1
  28. package/dist/VlmCandidateBuffer.d.ts +45 -6
  29. package/dist/VlmCandidateBuffer.d.ts.map +1 -1
  30. package/dist/VlmCandidateBuffer.js +76 -7
  31. package/dist/VlmCandidateBuffer.js.map +1 -1
  32. package/dist/analyzeCostObserver.d.ts +96 -0
  33. package/dist/analyzeCostObserver.d.ts.map +1 -0
  34. package/dist/analyzeCostObserver.js +130 -0
  35. package/dist/analyzeCostObserver.js.map +1 -0
  36. package/dist/config.d.ts +8 -0
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js.map +1 -1
  39. package/dist/connectorCadence.d.ts +286 -57
  40. package/dist/connectorCadence.d.ts.map +1 -1
  41. package/dist/connectorCadence.js +313 -61
  42. package/dist/connectorCadence.js.map +1 -1
  43. package/dist/deployedCadence.d.ts +8 -2
  44. package/dist/deployedCadence.d.ts.map +1 -1
  45. package/dist/deployedCadence.js +6 -3
  46. package/dist/deployedCadence.js.map +1 -1
  47. package/dist/deployedCadenceSource.d.ts +10 -0
  48. package/dist/deployedCadenceSource.d.ts.map +1 -1
  49. package/dist/deployedCadenceSource.js +11 -0
  50. package/dist/deployedCadenceSource.js.map +1 -1
  51. package/dist/diagnose.d.ts +1 -1
  52. package/dist/diagnose.js +1 -1
  53. package/dist/localInferenceSchema.d.ts +8 -3
  54. package/dist/localInferenceSchema.d.ts.map +1 -1
  55. package/dist/localInferenceSchema.js +8 -3
  56. package/dist/localInferenceSchema.js.map +1 -1
  57. package/dist/localSensing.d.ts +22 -1
  58. package/dist/localSensing.d.ts.map +1 -1
  59. package/dist/localSensing.js +101 -7
  60. package/dist/localSensing.js.map +1 -1
  61. package/dist/localVlmBench.d.ts +82 -0
  62. package/dist/localVlmBench.d.ts.map +1 -1
  63. package/dist/localVlmBench.js +88 -0
  64. package/dist/localVlmBench.js.map +1 -1
  65. package/dist/sensingCatchRate.d.ts.map +1 -1
  66. package/dist/sensingCatchRate.js +75 -10
  67. package/dist/sensingCatchRate.js.map +1 -1
  68. package/dist/sensingRunMode.d.ts.map +1 -1
  69. package/dist/sensingRunMode.js +38 -2
  70. package/dist/sensingRunMode.js.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,132 @@
1
+ """Fixture tests for the litert-lm constrained-decoding shim.
2
+
3
+ Runs with no litert-lm installed: a stand-in `litert_lm` module is planted on
4
+ sys.path so the import hook is exercised exactly as it is in production, and
5
+ the recorded kwargs prove WHICH configs are dropped and which survive.
6
+ """
7
+
8
+ import importlib
9
+ import os
10
+ import sys
11
+ import tempfile
12
+ import textwrap
13
+ import unittest
14
+
15
+ HERE = os.path.dirname(os.path.abspath(__file__))
16
+ sys.path.insert(0, HERE)
17
+
18
+ FAKE_LITERT_LM = textwrap.dedent(
19
+ '''
20
+ class ConstrainedDecodingConfig:
21
+ def __init__(self, enable=False, provider=None):
22
+ self.enable = enable
23
+ self.provider = provider
24
+
25
+ class Engine:
26
+ last_kwargs = None
27
+
28
+ def create_conversation(self, **kwargs):
29
+ Engine.last_kwargs = dict(kwargs)
30
+ return "conversation"
31
+ '''
32
+ )
33
+
34
+
35
+ class ShimTest(unittest.TestCase):
36
+ def setUp(self):
37
+ self.tmp = tempfile.TemporaryDirectory()
38
+ self.addCleanup(self.tmp.cleanup)
39
+ with open(os.path.join(self.tmp.name, "litert_lm.py"), "w") as fh:
40
+ fh.write(FAKE_LITERT_LM)
41
+ sys.path.insert(0, self.tmp.name)
42
+ self.addCleanup(lambda: sys.path.remove(self.tmp.name))
43
+ for name in ("litert_lm", "trillboards_litertlm_shim"):
44
+ sys.modules.pop(name, None)
45
+ self.addCleanup(lambda: [sys.modules.pop(n, None) for n in ("litert_lm", "trillboards_litertlm_shim")])
46
+ self._meta = list(sys.meta_path)
47
+ self.addCleanup(lambda: sys.meta_path.__setitem__(slice(None), self._meta))
48
+
49
+ def _load(self, **env):
50
+ old = {k: os.environ.get(k) for k in env}
51
+ os.environ.update({k: v for k, v in env.items() if v is not None})
52
+ for k, v in env.items():
53
+ if v is None:
54
+ os.environ.pop(k, None)
55
+
56
+ def restore():
57
+ for k, v in old.items():
58
+ if v is None:
59
+ os.environ.pop(k, None)
60
+ else:
61
+ os.environ[k] = v
62
+
63
+ self.addCleanup(restore)
64
+ return importlib.import_module("trillboards_litertlm_shim")
65
+
66
+ # ── the rule itself ────────────────────────────────────────────────────
67
+ def test_drops_only_the_no_response_format_shape(self):
68
+ shim = self._load(TRILLBOARDS_LITERTLM_SHIM="1")
69
+ import litert_lm
70
+
71
+ Cfg = litert_lm.ConstrainedDecodingConfig
72
+ self.assertTrue(shim.should_drop(Cfg(enable=True, provider=None)))
73
+ # a caller who asked for a response_format keeps their grammar
74
+ self.assertFalse(shim.should_drop(Cfg(enable=True, provider="LL_GUIDANCE")))
75
+ self.assertFalse(shim.should_drop(Cfg(enable=False, provider=None)))
76
+ self.assertFalse(shim.should_drop(None))
77
+ # truthy-but-not-True must NOT be treated as the upstream shape
78
+ self.assertFalse(shim.should_drop(Cfg(enable=1, provider=None)))
79
+
80
+ # ── the wrap, through a real import ────────────────────────────────────
81
+ def test_import_hook_wraps_and_drops(self):
82
+ self._load(TRILLBOARDS_LITERTLM_SHIM="1")
83
+ import litert_lm
84
+
85
+ cfg = litert_lm.ConstrainedDecodingConfig(enable=True, provider=None)
86
+ litert_lm.Engine().create_conversation(messages=[], constrained_decoding_config=cfg)
87
+ self.assertIsNone(litert_lm.Engine.last_kwargs["constrained_decoding_config"])
88
+ # every other kwarg is passed through untouched
89
+ self.assertEqual(litert_lm.Engine.last_kwargs["messages"], [])
90
+
91
+ def test_import_hook_preserves_a_real_response_format_request(self):
92
+ self._load(TRILLBOARDS_LITERTLM_SHIM="1")
93
+ import litert_lm
94
+
95
+ cfg = litert_lm.ConstrainedDecodingConfig(enable=True, provider="LL_GUIDANCE")
96
+ litert_lm.Engine().create_conversation(constrained_decoding_config=cfg)
97
+ self.assertIs(litert_lm.Engine.last_kwargs["constrained_decoding_config"], cfg)
98
+
99
+ def test_kill_switch_leaves_stock_behaviour(self):
100
+ self._load(TRILLBOARDS_LITERTLM_SHIM="0")
101
+ import litert_lm
102
+
103
+ cfg = litert_lm.ConstrainedDecodingConfig(enable=True, provider=None)
104
+ litert_lm.Engine().create_conversation(constrained_decoding_config=cfg)
105
+ self.assertIs(litert_lm.Engine.last_kwargs["constrained_decoding_config"], cfg)
106
+
107
+ def test_apply_is_idempotent(self):
108
+ shim = self._load(TRILLBOARDS_LITERTLM_SHIM="1")
109
+ import litert_lm
110
+
111
+ first = litert_lm.Engine.create_conversation
112
+ self.assertTrue(shim.apply_to(litert_lm))
113
+ self.assertIs(litert_lm.Engine.create_conversation, first)
114
+
115
+ # ── fails OPEN, never closed ───────────────────────────────────────────
116
+ def test_missing_engine_attribute_does_not_raise(self):
117
+ shim = self._load(TRILLBOARDS_LITERTLM_SHIM="1")
118
+
119
+ class NoEngine:
120
+ pass
121
+
122
+ self.assertFalse(shim.apply_to(NoEngine()))
123
+
124
+ def test_install_never_raises(self):
125
+ shim = self._load(TRILLBOARDS_LITERTLM_SHIM="1")
126
+ # a hostile meta_path must not take the serve down
127
+ sys.meta_path.insert(0, object())
128
+ self.assertIn(shim.install(), (True, False))
129
+
130
+
131
+ if __name__ == "__main__":
132
+ unittest.main()
@@ -0,0 +1 @@
1
+ import trillboards_litertlm_shim
@@ -0,0 +1,214 @@
1
+ """Drop the constrained-decoding setup `litert-lm serve` pays on EVERY request.
2
+
3
+ WHY THIS FILE EXISTS
4
+ --------------------
5
+ `litert-lm serve`'s OpenAI shim builds, once per HTTP request, at both
6
+ `openai_handler.py` construction sites (`/v1/chat/completions` and
7
+ `/v1/responses`, 0.15.0 / 0.16.0 / 0.16.1 alike)::
8
+
9
+ provider = LL_GUIDANCE if response_format is not None else None
10
+ constrained_decoding_config = ConstrainedDecodingConfig(enable=True, provider=provider)
11
+ with engine.create_conversation(..., constrained_decoding_config=cfg) as conv:
12
+
13
+ `enable` is **unconditionally True** — including when the caller sent no
14
+ `response_format` at all and the generation that follows is therefore
15
+ unconstrained. Enabling it makes the native `litert_lm_conversation_create`
16
+ build the constraint machinery for a grammar nothing will use, and it is
17
+ rebuilt for every conversation because the serve creates one per request.
18
+
19
+ MEASURED (Apple M4 Max 16c/48G, litert-lm 0.16.0, Gemma 4 E2B, GPU/Metal
20
+ delegate, vision_backend=gpu + speculative_decoding=true from config.json, the
21
+ two committed CCTV fixtures, operator/SOP field set, one engine, arms alternated
22
+ sample-by-sample with the leading arm rotated each round, n=10 pairs, load
23
+ average 22):
24
+
25
+ arm create_conversation send_message total
26
+ stock (enable=True) 0.717 s 0.560 s 1.275 s
27
+ this shim (dropped) 0.001 s 0.603 s 0.604 s
28
+
29
+ paired total ratio 2.194x (min 2.040, max 2.284)
30
+ generations BYTE-IDENTICAL 10/10; prefill 493 and decode 58 tokens on both
31
+
32
+ `send_message` is untouched — this removes setup, not sampling — and the
33
+ answers are the same bytes, which is the property that makes it safe.
34
+
35
+ THE RATIO IS ITSELF LOAD-DEPENDENT, and that is worth knowing before quoting
36
+ one: the same paired comparison through the shipped HTTP path gives 3.734x on a
37
+ box at 0.0% CPU idle and 2.42x at load 25, because the work this removes is
38
+ CPU-bound while the work it leaves is GPU-bound. The near-idle figure is the
39
+ honest one; a number taken under contention flatters this change.
40
+
41
+ The connector never sends `response_format`: LiteRtVlmProvider gets its
42
+ structured output by asking for a bare JSON object and validating client-side,
43
+ and it re-measured `response_format` at +35.2% p50 and DECLINED it. So on the
44
+ shipped path this setup is pure waste, every frame.
45
+
46
+ WHAT IT DOES NOT TOUCH
47
+ ----------------------
48
+ The rule fires on EXACTLY `enable is True and provider is None` — the shape the
49
+ shim builds when, and only when, the caller asked for no `response_format`. A
50
+ request that DOES carry one arrives as `provider=LL_GUIDANCE` and is passed
51
+ through unchanged, so constrained decoding still works for anyone who asks for
52
+ it. `ConstrainedDecodingConfig` is constructed nowhere else in the litert-lm
53
+ CLI (`run.py` and `gemini_handler.py` create conversations without one), so no
54
+ other command can reach this wrap.
55
+
56
+ HOW IT IS LOADED
57
+ ----------------
58
+ The three provisioners copy this file plus a one-line `.pth` into the litert-lm
59
+ tool venv's site-packages. That leaves every upstream file byte-identical on
60
+ disk, needs no change to how the serve is launched on any platform (the macOS
61
+ LaunchAgent, the systemd unit and the Windows Scheduled Task all still exec
62
+ `litert-lm serve`), and covers a serve started by hand as well as one started
63
+ by a service manager.
64
+
65
+ It is LAZY on purpose: `install()` only registers a `sys.meta_path` hook and
66
+ imports nothing, so it piggybacks on the import the CLI was going to do anyway
67
+ rather than adding one. `litert-lm list` and `litert-lm --version` — which the
68
+ provisioners run as health checks — keep exactly the startup cost they had.
69
+
70
+ It also fails OPEN. Every failure path leaves stock behaviour intact and prints
71
+ one line to stderr, which the serve's own log captures: a serve that is merely
72
+ slow keeps sensing, and a serve that refuses to start senses nothing. The line
73
+ is the only way to tell the two apart from the outside, so it is not optional —
74
+ grep the serve log for `[trillboards-litertlm-shim]`.
75
+
76
+ Set `TRILLBOARDS_LITERTLM_SHIM=0` to disable it without uninstalling anything.
77
+ """
78
+
79
+ from __future__ import annotations
80
+
81
+ import os
82
+ import sys
83
+
84
+ __all__ = ["install", "apply_to", "should_drop", "TAG"]
85
+
86
+ TAG = "[trillboards-litertlm-shim]"
87
+
88
+ #: The module whose import triggers the wrap. `Engine.create_conversation` is
89
+ #: the single funnel every serve request goes through.
90
+ TARGET_MODULE = "litert_lm"
91
+
92
+ #: Marker set on the wrapper so a second install() is a no-op rather than a
93
+ #: second layer of indirection.
94
+ _MARKER = "_trillboards_litertlm_shim"
95
+
96
+
97
+ def _say(message: str) -> None:
98
+ """One line to stderr. Never raises — a logging failure must not be fatal."""
99
+ try:
100
+ sys.stderr.write(f"{TAG} {message}\n")
101
+ sys.stderr.flush()
102
+ except Exception: # pragma: no cover - stderr is gone; nothing to do
103
+ pass
104
+
105
+
106
+ def _enabled() -> bool:
107
+ """False only when explicitly switched off, so a missing var means ON."""
108
+ return os.environ.get("TRILLBOARDS_LITERTLM_SHIM", "1").strip() not in ("0", "false", "no")
109
+
110
+
111
+ def should_drop(config: object) -> bool:
112
+ """Is this the no-response_format shape litert-lm builds on every request?
113
+
114
+ Deliberately exact. `enable is True` and not merely truthy, `provider is
115
+ None` and not merely falsy: anything else is a caller who asked for
116
+ constrained decoding, and dropping theirs would change an answer rather
117
+ than a setup cost.
118
+ """
119
+ if config is None:
120
+ return False
121
+ return getattr(config, "enable", None) is True and getattr(config, "provider", "?") is None
122
+
123
+
124
+ def apply_to(litert_lm_module: object) -> bool:
125
+ """Wrap `Engine.create_conversation` on an imported litert_lm. Idempotent.
126
+
127
+ Returns True when the wrap is in place (including when it already was).
128
+ """
129
+ engine_cls = getattr(litert_lm_module, "Engine", None)
130
+ original = getattr(engine_cls, "create_conversation", None)
131
+ if original is None:
132
+ _say("NOT ACTIVE: litert_lm.Engine.create_conversation is absent — "
133
+ "upstream shape changed; serving with stock (slower) behaviour")
134
+ return False
135
+ if getattr(original, _MARKER, False):
136
+ return True
137
+
138
+ def create_conversation(self, *args, **kwargs):
139
+ if should_drop(kwargs.get("constrained_decoding_config")):
140
+ kwargs["constrained_decoding_config"] = None
141
+ return original(self, *args, **kwargs)
142
+
143
+ # Not functools.wraps: the marker must survive, and copying __wrapped__
144
+ # would let a second install() unwrap back to the stock method.
145
+ create_conversation.__name__ = getattr(original, "__name__", "create_conversation")
146
+ create_conversation.__doc__ = getattr(original, "__doc__", None)
147
+ setattr(create_conversation, _MARKER, True)
148
+ setattr(engine_cls, "create_conversation", create_conversation)
149
+ return True
150
+
151
+
152
+ def _install_now(module: object) -> None:
153
+ if apply_to(module):
154
+ _say("ACTIVE: unconstrained conversations skip constrained-decoding setup")
155
+
156
+
157
+ class _LitertLmImportHook:
158
+ """Applies the wrap immediately after `litert_lm` finishes importing.
159
+
160
+ A `find_spec` that answers only for the target module, hands the real
161
+ machinery back the job of finding it, and decorates the resulting loader's
162
+ `exec_module` so the wrap lands after the module body has run. It removes
163
+ itself first, so it can never recurse and never slows a later import.
164
+ """
165
+
166
+ def find_spec(self, fullname, path=None, target=None): # noqa: D401
167
+ if fullname != TARGET_MODULE:
168
+ return None
169
+ try:
170
+ sys.meta_path.remove(self)
171
+ except ValueError:
172
+ return None
173
+ try:
174
+ import importlib.util
175
+
176
+ spec = importlib.util.find_spec(fullname)
177
+ if spec is None or spec.loader is None or not hasattr(spec.loader, "exec_module"):
178
+ return None
179
+ real_exec_module = spec.loader.exec_module
180
+
181
+ def exec_module(module):
182
+ real_exec_module(module)
183
+ try:
184
+ _install_now(module)
185
+ except Exception as exc: # noqa: BLE001
186
+ _say(f"NOT ACTIVE: {exc!r} — serving with stock (slower) behaviour")
187
+
188
+ spec.loader.exec_module = exec_module
189
+ return spec
190
+ except Exception as exc: # noqa: BLE001
191
+ _say(f"NOT ACTIVE: {exc!r} — serving with stock (slower) behaviour")
192
+ return None
193
+
194
+
195
+ def install() -> bool:
196
+ """Register the import hook. Safe to call more than once. Never raises."""
197
+ try:
198
+ if not _enabled():
199
+ _say("disabled by TRILLBOARDS_LITERTLM_SHIM=0")
200
+ return False
201
+ already = sys.modules.get(TARGET_MODULE)
202
+ if already is not None:
203
+ _install_now(already)
204
+ return True
205
+ if any(isinstance(f, _LitertLmImportHook) for f in sys.meta_path):
206
+ return True
207
+ sys.meta_path.insert(0, _LitertLmImportHook())
208
+ return True
209
+ except Exception as exc: # noqa: BLE001
210
+ _say(f"NOT ACTIVE: {exc!r} — serving with stock (slower) behaviour")
211
+ return False
212
+
213
+
214
+ install()
@@ -6,10 +6,14 @@
6
6
  #
7
7
  # WHY IT EXISTS. Local-only inference makes the box spec the product spec, and
8
8
  # every latency number in the capacity plan came off one Apple M4 Max
9
- # workstation — including the 7.30 s/frame in connectorCadence.ts that sizes the
10
- # whole 493-analyses/hour budget. A 3-core / 7 GB Apple silicon runner is the
11
- # closest CI has to the Mac mini we would actually recommend, and it is the only
12
- # cheap way to test the "16 GB+ RAM" minimum the activation runbook publishes.
9
+ # workstation — including the 7.30 s/frame in connectorCadence.ts, which is now
10
+ # the BOOTSTRAP a connector uses until it has measured itself (see
11
+ # analyzeCostObserver.ts) rather than a fleet-wide fact. A 3-core / 7 GB Apple
12
+ # silicon runner is the closest CI has to the Mac mini we would actually
13
+ # recommend, and its 7 GB is why it is also the cheapest evidence about memory:
14
+ # the "16 GB+ RAM" minimum this used to point at was RETIRED on 2026-08-18 —
15
+ # cctv-rushi-activation.md now publishes 8 GB, against a measured working set of
16
+ # 2.7 GB stock and 3.31 GB on the shipped GPU-vision path.
13
17
  #
14
18
  # It does NOT use setup-litertlm.sh's LaunchAgent path: that bootstraps a
15
19
  # per-login agent, and a CI runner's session is not the session a customer logs
@@ -398,6 +398,10 @@ cp "$PKG_ROOT/deploy/macos/com.trillboards.edge.plist" "$PAYLOAD/share/com.trill
398
398
  # verifies the private service before it opens camera pairing.
399
399
  cp "$PKG_ROOT/deploy/macos/com.trillboards.litertlm.plist" "$PAYLOAD/share/com.trillboards.litertlm.plist"
400
400
  cp "$PKG_ROOT/deploy/macos/scripts/setup-litertlm.sh" "$PAYLOAD/share/setup-litertlm.sh"
401
+ # Staged NEXT TO setup-litertlm.sh because that is where install_litertlm_shim
402
+ # looks first. It is copied into the litert-lm tool venv's site-packages at
403
+ # provisioning time; nothing else reads it, and no upstream file is modified.
404
+ cp "$PKG_ROOT/deploy/litertlm/trillboards_litertlm_shim.py" "$PAYLOAD/share/trillboards_litertlm_shim.py"
401
405
  cp "$PKG_ROOT/deploy/macos/scripts/ensure-litertlm.sh" "$PAYLOAD/share/ensure-litertlm.sh"
402
406
  cp "$PKG_ROOT/deploy/macos/apply-update.sh" "$PAYLOAD/share/apply-update.sh"
403
407
  chmod +x "$PAYLOAD/share/setup-litertlm.sh" "$PAYLOAD/share/ensure-litertlm.sh" \
@@ -66,8 +66,23 @@ fail() { log "ERROR: $*"; exit 1; }
66
66
  # `litert-lm serve` takes NO flags for any of this: `serve` accepts only
67
67
  # --host/--port/--api/--cors-origin/--verbose. Every engine knob it honours comes
68
68
  # from `$LITERT_LM_DIR/config.json`, which `litert_lm_cli.config.get_model_config`
69
- # merges per model id. So this file IS the tuning surface, and writing it is the
70
- # whole change -- the serve stays stock.
69
+ # merges per model id. So this file IS the tuning surface for everything it can
70
+ # express, and the serve binary stays stock: we pass it no flags and we modify
71
+ # none of its files.
72
+ #
73
+ # ONE knob is NOT expressible there, and it is the largest one left. Every
74
+ # request the serve handles gets `ConstrainedDecodingConfig(enable=True, ...)`
75
+ # built for it unconditionally -- `enable` is True even when the caller sent no
76
+ # `response_format` and the generation is therefore unconstrained -- so the
77
+ # native conversation-create builds constraint machinery for a grammar nothing
78
+ # will use, once per frame. `config_schema.json` on the pinned 0.16.0 has 17
79
+ # keys (backend, cpu_thread_count, audio_backend, vision_backend, cache,
80
+ # max_num_tokens, temperature, top_p, top_k, seed, gpu_decode_steps_per_sync,
81
+ # speculative_decoding, thinking, thinking_budget, activation_data_type, plus
82
+ # the `default`/`models` containers) and NONE of them reaches it; no request
83
+ # field reaches it either, and both 0.15.0 and 0.16.1 construct it identically,
84
+ # so a version move does not reach it. That is why install_litertlm_shim below
85
+ # exists -- see it for the measurement and for why it is not a patch.
71
86
  #
72
87
  # Two settings, both MEASURED on an M4 Max (12P+4E/48G), litert-lm 0.16.0, Gemma
73
88
  # 4 E2B, delegate asserted GPU from the native log, 16 warm-ups before timing,
@@ -126,6 +141,86 @@ write_litertlm_config() {
126
141
  log "wrote $CONFIG_JSON (vision_backend=${_vision:-cpu-default}, speculative_decoding=true)"
127
142
  }
128
143
 
144
+ # -- The one knob config.json cannot express -----------------------------------
145
+ #
146
+ # `litert-lm serve` builds `ConstrainedDecodingConfig(enable=True, provider=...)`
147
+ # for EVERY request, at both openai_handler.py construction sites, with `enable`
148
+ # hardcoded True -- so a request that carried no `response_format`, which is
149
+ # every request the connector sends, still makes the native conversation-create
150
+ # build constraint machinery for a grammar nothing will use. It is rebuilt per
151
+ # request because the serve creates one conversation per request.
152
+ #
153
+ # MEASURED (M4 Max 16c/48G, litert-lm 0.16.0, Gemma 4 E2B, GPU/Metal, vision_backend
154
+ # =gpu + speculative_decoding=true, operator/SOP field set, the two committed CCTV
155
+ # fixtures, arms alternated sample-by-sample with the leading arm rotated each
156
+ # round, load average 22-25):
157
+ #
158
+ # in-process, one engine, n=10 pairs
159
+ # arm create_conversation send_message total
160
+ # stock 0.717 s 0.560 s 1.275 s
161
+ # without it 0.001 s 0.603 s 0.604 s
162
+ # paired 2.194x (min 2.040, max 2.284); generations BYTE-IDENTICAL 10/10;
163
+ # prefill 493 / decode 58 on both arms
164
+ #
165
+ # through two stock serves on two ports, interleaved, n=16 pairs
166
+ # 1.309 s -> 0.592 s p50 (p95 3.664 -> 0.965), paired 2.42x (min 2.174, max
167
+ # 4.657), 16/16 schema-valid and 16/16 byte-identical on both arms
168
+ #
169
+ # bench-local-vlm.mjs --arms gpu-sop, 16 warm-ups, n=8, back to back
170
+ # 1.32 s/frame (2,727 analyses/hour) -> 0.72 s/frame (5,000/hour), 8/8
171
+ # schema-valid and 83.3% field accuracy on BOTH arms
172
+ #
173
+ # A speed change, not an answer change -- the same bar speculative_decoding had
174
+ # to clear. The RATIO is itself load-dependent (3.734x at 0.0% CPU idle vs 2.42x
175
+ # at load 25, because the removed work is CPU-bound and the retained work is
176
+ # GPU-bound), so quote the near-idle figure.
177
+ #
178
+ # We do NOT patch upstream. Two files are ADDED to the litert-lm tool venv's
179
+ # site-packages: our shim, and a one-line .pth that imports it. Every upstream
180
+ # file stays byte-identical, no launch definition changes on any platform (so
181
+ # the model OTA's `launchctl kickstart com.trillboards.litertlm` still finds the
182
+ # same label), and a serve started by hand gets the fix too. The shim is lazy --
183
+ # it only registers an import hook, so `litert-lm list`/`--version`, which this
184
+ # script runs as health checks, do not gain a dylib load. It fails OPEN: any
185
+ # failure leaves stock behaviour and prints one line to the serve's own log, and
186
+ # `TRILLBOARDS_LITERTLM_SHIM=0` disables it without uninstalling anything.
187
+ #
188
+ # Runs UNCONDITIONALLY, never inside the install/repair branch: `uv tool install
189
+ # --force` rebuilds the venv and takes both files with it, and the healthy-CLI
190
+ # path skips that branch entirely -- so gating this behind it is exactly how a
191
+ # repaired-and-healthy machine would keep the slow serve while reporting fine.
192
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || echo .)"
193
+ find_litertlm_shim() {
194
+ for _c in "$SCRIPT_DIR/trillboards_litertlm_shim.py" \
195
+ "$SCRIPT_DIR/../litertlm/trillboards_litertlm_shim.py" \
196
+ "$SCRIPT_DIR/../../litertlm/trillboards_litertlm_shim.py"; do
197
+ [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
198
+ done
199
+ return 1
200
+ }
201
+ install_litertlm_shim() {
202
+ _shim="$(find_litertlm_shim)" || { log "WARN: litert-lm shim source not found; serve keeps stock (slower) conversation setup"; return 1; }
203
+ _tooldir="$("$UV" tool dir 2>/dev/null | tr -d '\r')"
204
+ [ -n "$_tooldir" ] || { log "WARN: could not resolve \`uv tool dir\`; skipping litert-lm shim"; return 1; }
205
+ _py="$_tooldir/litert-lm/bin/python"
206
+ [ -x "$_py" ] || { log "WARN: litert-lm venv python missing at $_py; skipping shim"; return 1; }
207
+ _site="$("$_py" -c 'import sysconfig;print(sysconfig.get_paths()["purelib"])' 2>/dev/null | tr -d '\r')"
208
+ [ -n "$_site" ] && [ -d "$_site" ] || { log "WARN: could not resolve litert-lm site-packages; skipping shim"; return 1; }
209
+ cp -f "$_shim" "$_site/trillboards_litertlm_shim.py" 2>/dev/null \
210
+ || { log "WARN: could not copy the litert-lm shim into $_site"; return 1; }
211
+ printf 'import trillboards_litertlm_shim\n' > "$_site/trillboards-litertlm-shim.pth" 2>/dev/null \
212
+ || { log "WARN: could not write the litert-lm shim .pth"; return 1; }
213
+ # Capability is established by ATTEMPTING THE OPERATION. Importing litert_lm
214
+ # here is the only thing that proves the wrap actually landed on the class the
215
+ # serve will use; a file that merely exists proves nothing.
216
+ if "$_py" -c 'import litert_lm,sys; sys.exit(0 if getattr(litert_lm.Engine.create_conversation,"_trillboards_litertlm_shim",False) else 1)' >>"$LOG" 2>&1; then
217
+ log "litert-lm fast-conversation shim active in $_site"
218
+ return 0
219
+ fi
220
+ log "WARN: litert-lm shim did not take effect; serve keeps stock (slower) conversation setup"
221
+ return 1
222
+ }
223
+
129
224
  # One real image inference against the running serve. A 200 means the vision
130
225
  # encoder loaded AND ran on whatever backend config.json just asked for.
131
226
  # Deliberately NOT a /v1/models check: that answers 200 without ever building an
@@ -332,6 +427,8 @@ else
332
427
  log "litert-lm CLI repaired successfully at $("$LITERTLM" --version 2>/dev/null | awk '{print $NF}')"
333
428
  fi
334
429
 
430
+ install_litertlm_shim || true
431
+
335
432
  # 3. Model - import the verified bundled file. Idempotent: skip entirely if
336
433
  # litert-lm already has the model. Engineering runs may use HF only with the
337
434
  # explicit TRILLBOARDS_ALLOW_MODEL_DOWNLOAD=1 opt-in.
@@ -74,8 +74,23 @@ fail() { log "ERROR: $*"; exit 1; }
74
74
  # `litert-lm serve` takes NO flags for any of this: `serve` accepts only
75
75
  # --host/--port/--api/--cors-origin/--verbose. Every engine knob it honours comes
76
76
  # from `$LITERT_LM_DIR/config.json`, which `litert_lm_cli.config.get_model_config`
77
- # merges per model id. So this file IS the tuning surface, and writing it is the
78
- # whole change -- the serve stays stock.
77
+ # merges per model id. So this file IS the tuning surface for everything it can
78
+ # express, and the serve binary stays stock: we pass it no flags and we modify
79
+ # none of its files.
80
+ #
81
+ # ONE knob is NOT expressible there, and it is the largest one left. Every
82
+ # request the serve handles gets `ConstrainedDecodingConfig(enable=True, ...)`
83
+ # built for it unconditionally -- `enable` is True even when the caller sent no
84
+ # `response_format` and the generation is therefore unconstrained -- so the
85
+ # native conversation-create builds constraint machinery for a grammar nothing
86
+ # will use, once per frame. `config_schema.json` on the pinned 0.16.0 has 17
87
+ # keys (backend, cpu_thread_count, audio_backend, vision_backend, cache,
88
+ # max_num_tokens, temperature, top_p, top_k, seed, gpu_decode_steps_per_sync,
89
+ # speculative_decoding, thinking, thinking_budget, activation_data_type, plus
90
+ # the `default`/`models` containers) and NONE of them reaches it; no request
91
+ # field reaches it either, and both 0.15.0 and 0.16.1 construct it identically,
92
+ # so a version move does not reach it. That is why install_litertlm_shim below
93
+ # exists -- see it for the measurement and for why it is not a patch.
79
94
  #
80
95
  # Two settings, both MEASURED on an M4 Max (12P+4E/48G), litert-lm 0.16.0, Gemma
81
96
  # 4 E2B, delegate asserted GPU from the native log, 16 warm-ups before timing,
@@ -134,6 +149,86 @@ write_litertlm_config() {
134
149
  log "wrote $CONFIG_JSON (vision_backend=${_vision:-cpu-default}, speculative_decoding=true)"
135
150
  }
136
151
 
152
+ # -- The one knob config.json cannot express -----------------------------------
153
+ #
154
+ # `litert-lm serve` builds `ConstrainedDecodingConfig(enable=True, provider=...)`
155
+ # for EVERY request, at both openai_handler.py construction sites, with `enable`
156
+ # hardcoded True -- so a request that carried no `response_format`, which is
157
+ # every request the connector sends, still makes the native conversation-create
158
+ # build constraint machinery for a grammar nothing will use. It is rebuilt per
159
+ # request because the serve creates one conversation per request.
160
+ #
161
+ # MEASURED (M4 Max 16c/48G, litert-lm 0.16.0, Gemma 4 E2B, GPU/Metal, vision_backend
162
+ # =gpu + speculative_decoding=true, operator/SOP field set, the two committed CCTV
163
+ # fixtures, arms alternated sample-by-sample with the leading arm rotated each
164
+ # round, load average 22-25):
165
+ #
166
+ # in-process, one engine, n=10 pairs
167
+ # arm create_conversation send_message total
168
+ # stock 0.717 s 0.560 s 1.275 s
169
+ # without it 0.001 s 0.603 s 0.604 s
170
+ # paired 2.194x (min 2.040, max 2.284); generations BYTE-IDENTICAL 10/10;
171
+ # prefill 493 / decode 58 on both arms
172
+ #
173
+ # through two stock serves on two ports, interleaved, n=16 pairs
174
+ # 1.309 s -> 0.592 s p50 (p95 3.664 -> 0.965), paired 2.42x (min 2.174, max
175
+ # 4.657), 16/16 schema-valid and 16/16 byte-identical on both arms
176
+ #
177
+ # bench-local-vlm.mjs --arms gpu-sop, 16 warm-ups, n=8, back to back
178
+ # 1.32 s/frame (2,727 analyses/hour) -> 0.72 s/frame (5,000/hour), 8/8
179
+ # schema-valid and 83.3% field accuracy on BOTH arms
180
+ #
181
+ # A speed change, not an answer change -- the same bar speculative_decoding had
182
+ # to clear. The RATIO is itself load-dependent (3.734x at 0.0% CPU idle vs 2.42x
183
+ # at load 25, because the removed work is CPU-bound and the retained work is
184
+ # GPU-bound), so quote the near-idle figure.
185
+ #
186
+ # We do NOT patch upstream. Two files are ADDED to the litert-lm tool venv's
187
+ # site-packages: our shim, and a one-line .pth that imports it. Every upstream
188
+ # file stays byte-identical, no launch definition changes on any platform (the
189
+ # systemd unit's ExecStart is untouched), and a serve started by hand gets the
190
+ # fix too. The shim is lazy --
191
+ # it only registers an import hook, so `litert-lm list`/`--version`, which this
192
+ # script runs as health checks, do not gain a dylib load. It fails OPEN: any
193
+ # failure leaves stock behaviour and prints one line to the serve's own log, and
194
+ # `TRILLBOARDS_LITERTLM_SHIM=0` disables it without uninstalling anything.
195
+ #
196
+ # Runs UNCONDITIONALLY, never inside the install/repair branch: `uv tool install
197
+ # --force` rebuilds the venv and takes both files with it, and the healthy-CLI
198
+ # path skips that branch entirely -- so gating this behind it is exactly how a
199
+ # repaired-and-healthy machine would keep the slow serve while reporting fine.
200
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || echo .)"
201
+ find_litertlm_shim() {
202
+ for _c in "$SCRIPT_DIR/trillboards_litertlm_shim.py" \
203
+ "$SCRIPT_DIR/../litertlm/trillboards_litertlm_shim.py" \
204
+ "$SCRIPT_DIR/../../litertlm/trillboards_litertlm_shim.py"; do
205
+ [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
206
+ done
207
+ return 1
208
+ }
209
+ install_litertlm_shim() {
210
+ _shim="$(find_litertlm_shim)" || { log "WARN: litert-lm shim source not found; serve keeps stock (slower) conversation setup"; return 1; }
211
+ _tooldir="$("$UV" tool dir 2>/dev/null | tr -d '\r')"
212
+ [ -n "$_tooldir" ] || { log "WARN: could not resolve \`uv tool dir\`; skipping litert-lm shim"; return 1; }
213
+ _py="$_tooldir/litert-lm/bin/python"
214
+ [ -x "$_py" ] || { log "WARN: litert-lm venv python missing at $_py; skipping shim"; return 1; }
215
+ _site="$("$_py" -c 'import sysconfig;print(sysconfig.get_paths()["purelib"])' 2>/dev/null | tr -d '\r')"
216
+ [ -n "$_site" ] && [ -d "$_site" ] || { log "WARN: could not resolve litert-lm site-packages; skipping shim"; return 1; }
217
+ cp -f "$_shim" "$_site/trillboards_litertlm_shim.py" 2>/dev/null \
218
+ || { log "WARN: could not copy the litert-lm shim into $_site"; return 1; }
219
+ printf 'import trillboards_litertlm_shim\n' > "$_site/trillboards-litertlm-shim.pth" 2>/dev/null \
220
+ || { log "WARN: could not write the litert-lm shim .pth"; return 1; }
221
+ # Capability is established by ATTEMPTING THE OPERATION. Importing litert_lm
222
+ # here is the only thing that proves the wrap actually landed on the class the
223
+ # serve will use; a file that merely exists proves nothing.
224
+ if "$_py" -c 'import litert_lm,sys; sys.exit(0 if getattr(litert_lm.Engine.create_conversation,"_trillboards_litertlm_shim",False) else 1)' >>"$LOG" 2>&1; then
225
+ log "litert-lm fast-conversation shim active in $_site"
226
+ return 0
227
+ fi
228
+ log "WARN: litert-lm shim did not take effect; serve keeps stock (slower) conversation setup"
229
+ return 1
230
+ }
231
+
137
232
  # One real image inference against the running serve. A 200 means the vision
138
233
  # encoder loaded AND ran on whatever backend config.json just asked for.
139
234
  # Deliberately NOT a /v1/models check: that answers 200 without ever building an
@@ -226,6 +321,8 @@ else
226
321
  log "litert-lm CLI repaired at $("$LITERTLM" --version 2>/dev/null | awk '{print $NF}')"
227
322
  fi
228
323
 
324
+ install_litertlm_shim || true
325
+
229
326
  # 3. Model. Idempotent: skip entirely if litert-lm already has it.
230
327
  HAVE_MODEL=0
231
328
  if "$LITERTLM" list 2>/dev/null | grep -q "$MODEL_ID"; then
@@ -222,6 +222,10 @@ Log "Customer setup window smoke test passed"
222
222
  # model path from the customer SFX and verifies local AI before pairing; the
223
223
  # serve task keeps `litert-lm serve :9379` alive for local-sense.
224
224
  Copy-Item -Force "$Here\setup-litertlm.ps1" "$Payload\bin\setup-litertlm.ps1"
225
+ # Staged NEXT TO setup-litertlm.ps1 because that is where Install-LitertlmShim
226
+ # looks first. Copied into the litert-lm tool venv's site-packages at
227
+ # provisioning time; nothing else reads it, and no upstream file is modified.
228
+ Copy-Item -Force "$Here\..\litertlm\trillboards_litertlm_shim.py" "$Payload\bin\trillboards_litertlm_shim.py"
225
229
  Copy-Item -Force "$Here\register-litertlm-task.ps1" "$Payload\bin\register-litertlm-task.ps1"
226
230
  Copy-Item -Force "$Here\check-feeds.js" "$Payload\bin\check-feeds.js"
227
231