news_transformer: clamp ORT threads, disable intra-op spinning; spec marked implemented in ForexBot2

Same two load_model lines as the copy vendored into ForexBot2/news_model.py
(2026-09-27), so the files stay identical on the scoring functions.
This commit is contained in:
Harwinder Sekhon
2026-09-27 20:46:27 -04:00
parent 10e7ba57f1
commit 2787b3cb8b
2 changed files with 28 additions and 4 deletions
+23 -3
View File
@@ -1,8 +1,28 @@
# Specification: run the ML-NEWS XAU transformer inside ForexBot2 # Specification: run the ML-NEWS XAU transformer inside ForexBot2
Status: **specification only, not implemented.** Written 2026-09-27 so a coding agent can implement it Status: **IMPLEMENTED 2026-09-27** (ForexBot2 branch `sql-integration`, on top of `6af1237`; suite
later without this conversation. Every file:line reference was checked against the repos on that date; Module 38). The text below is the specification as written before the implementation; file:line
re-verify before editing, the numbers drift. numbers refer to `6af1237` and have drifted. Where the implementation differs, this list wins:
- **`primary` mode does not archive the cycle's rows.** Owner decision during implementation: once the
local model scores, `logs/newsdata.db` stops growing, because the model's own output is not training
data. Only the DeepSeek audit copies (`deepseek_audit_per_day`, shipped 0) are written. `off` and
`fallback` archive as before (a `fallback` model row carries `model_teacher`). §5.4 and §9 read
accordingly.
- `_audit_with_deepseek` **returns** the `llm` copies; there is no `_pending_audit_rows` attribute.
The mode switch of §5.2 lives in `NewsEngine._enrich_headline_scores(scored, mode)`, which returns
those copies, so the call site and the tests share one function.
- `config.json.example` ships `"mode": "off"`; the live server sets `primary`
(`deploy/README-news-model.md`).
- §8 step 0 was wrong: the dashboard daemon never writes the archive in any mode
(`set_db_writes_for_process(False)`), so its DeepSeek rows are not "free audit labels". Both
processes load the model (owner decision); `NEWS_MODEL_MODE=off` stays as the memory fallback.
- `warm_up_in_background()` is implemented (module-level once flag; a `NewsEngine` is built per scan).
- `news_model.get_news_model()` also treats a missing `path` as a load failure (one warning).
- `requirements.txt` floors: `onnxruntime>=1.30,<2`, `tokenizers>=0.23.2,<1` (the versions that
verified the bundle).
- ML-NEWS `news_transformer.py` received the §3.4 edits in the same session; the vendored copy differs
only in `load_model` having no default directory.
Repos: Repos:
- **ML-NEWS** `~/Development/ML-NEWS` (git `ssh://git@192.168.0.11:222/harry/News-ML-Decoder.git`). - **ML-NEWS** `~/Development/ML-NEWS` (git `ssh://git@192.168.0.11:222/harry/News-ML-Decoder.git`).
+5 -1
View File
@@ -25,6 +25,7 @@ from __future__ import annotations
import argparse import argparse
import html import html
import json import json
import os
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any, Iterable from typing import Any, Iterable
@@ -68,8 +69,11 @@ def load_model(model_dir: str | Path = DEFAULT_MODEL_DIR,
tokenizer.enable_padding(pad_id=int(meta["pad_token_id"]), pad_token=meta["pad_token"]) tokenizer.enable_padding(pad_id=int(meta["pad_token_id"]), pad_token=meta["pad_token"])
options = ort.SessionOptions() options = ort.SessionOptions()
if threads: if threads:
options.intra_op_num_threads = threads options.intra_op_num_threads = max(1, min(int(threads), os.cpu_count() or 1))
options.inter_op_num_threads = 1 options.inter_op_num_threads = 1
# ORT intra-op threads spin-wait after each run by default; in a mostly
# idle bot process that is 4 cores at 100% between cycles.
options.add_session_config_entry("session.intra_op.allow_spinning", "0")
session = ort.InferenceSession(str(model_dir / meta.get("onnx_file", "model.onnx")), session = ort.InferenceSession(str(model_dir / meta.get("onnx_file", "model.onnx")),
options, providers=["CPUExecutionProvider"]) options, providers=["CPUExecutionProvider"])
return {"meta": meta, "tokenizer": tokenizer, "session": session} return {"meta": meta, "tokenizer": tokenizer, "session": session}