Repository navigation
feat(mtp-policy): expose the settled MTP verdict through a supported interface #1257
Description
Problem / Background
MtpPolicy (issue #333, src/server/batch/mtp_policy.rs) settles a per-pairing MTP verdict on the actual machine and persists it as a PolicyHint under ${MLXCEL_CACHE_DIR:-$HOME/.cache/mlxcel}/mtp-policy/<key-hash>.json. That verdict is the only honest, machine-specific answer to "does MTP help for this pairing here", and it is exactly what a host application wants to show a user.
Backend.AI GO is about to surface it (lablup/backend.ai-go#4441, epic lablup/backend.ai-go#4429). There is no API for it, so the only available path is to read mlxcel's cache directory from another process: enumerate mtp-policy/*.json, parse each file, and match on the body fields (target, drafter, block_size) rather than recomputing the SHA256 file stem.
That works, and it is coupling to a private on-disk format. HINT_VERSION is documented to bump whenever the schema or the verdict semantics change, HINT_SUBDIR is a private constant, and PolicyHint is pub(crate). Any of those can change in a patch release without notice, and the consumer has no way to learn about it except by silently showing nothing. The consumer already treats an unknown version, a missing directory, an unparseable file, and a block_size mismatch as "no verdict yet" rather than an error, so a format change degrades to a blank surface rather than a crash, but a blank surface is still a regression the user sees and neither project detects.
Proposed Solution
Expose the settled verdict through a supported interface. Either of these closes the gap; the first is preferred.
Option A, server API (preferred). Add a read-only endpoint that reports the current pairing's policy state, for example GET /v1/internal/mtp-policy returning the active (target, drafter, hardware, block_size) key plus verdict, acceptance_rate, samples, and whether the policy is still profiling (with PROFILE_SAMPLE_TARGET so a consumer can say how many more single-request generations are needed). A consumer then reads the verdict from the process that owns it, over the same transport it already uses, with no filesystem coupling and no cache-root resolution.
Option B, documented file contract. Keep the cache files as the interface but make them a contract: document the directory layout, the PolicyHint body, and the versioning rule in the repository docs; state which fields are stable; and treat a change to them as a breaking change with a HINT_VERSION bump plus a changelog entry.
Either way, the "still profiling" state deserves to be expressible. Today a consumer can only infer it from the absence of a file, which is indistinguishable from "the cache root resolved to the wrong place".
Acceptance Criteria
- The settled MTP verdict for the running pairing is readable through a supported interface (API endpoint or documented file contract)
- The interface distinguishes "settled: enable", "settled: decline", and "still profiling", and reports how many qualifying samples remain when profiling
- The reported payload carries enough of the key (
target,drafter,hardware,block_size) for a consumer to confirm the verdict describes the pairing it is showing - Versioning and the compatibility policy for the interface are documented
- Backend.AI GO's interim cache read can be replaced by the supported interface
Implementation notes
Option A landed as GET /v1/internal/mtp-policy (src/server/routes/mtp_policy.rs), documented in docs/mtp-policy-api.md including the schema_version compatibility policy. The on-disk hints are untouched and keep working, so the shipped Backend.AI GO reader (lablup/backend.ai-go#4441) is not broken by this change; it can migrate to the endpoint at its own pace.
Beyond the three states the body asks for, the endpoint reports a fourth, forced (MLXCEL_ENABLE_MTP_B1 pinned the decision), separately from settled, because an operator pin is not a measured verdict. It also reports unavailable with a reason (no_mtp_dispatch, adaptive_disabled, worker_not_ready), which is what removes the "still profiling is indistinguishable from the cache root resolved to the wrong place" ambiguity named above.
Technical Considerations
Consumer side: lablup/backend.ai-go#4441 ships the cache read as the interim path, with a module doc comment naming this issue and stating plainly that it couples to a private on-disk format. Replacing it with whichever interface lands here is a small, localized change on that side.
Relevant constants at 4968b72e: HINT_VERSION = 3, HINT_SUBDIR = "mtp-policy", PROFILE_SAMPLE_TARGET = 4, ENABLE_SPEEDUP_FLOOR = 1.5, DECLINE_SPEEDUP_CEIL = 1.0. Cache root resolution is mlxcel_core::cache_root() (MLXCEL_CACHE_DIR, else $HOME/.cache/mlxcel).
Refs #333
Activity
inureyes commentedon Aug 19, 2026
Consumer side has landed: lablup/backend.ai-go#4441 reads the settled verdict from
${MLXCEL_CACHE_DIR:-$HOME/.cache/mlxcel}/mtp-policy/*.jsonas the interim path, matching on the hint body (target,drafter,block_size) rather than recomputing the SHA256 file stem, and treating a missing directory, an unreadable or unparseable file, aversionother than 3, and ablock_sizemismatch all as "no verdict yet" rather than as errors. The reader's module doc states plainly that this couples to a private on-disk format and names this issue as its replacement.docs(reports): add the PR #1268 technical report
fix(server): report the exactness veto at /v1/internal/mtp-policy ins…
feat(mtp-policy): expose the settled verdict at /v1/internal/mtp-poli…
feat(mtp-policy): expose the settled verdict at /v1/internal/mtp-poli…
fix(server): report the exactness veto at /v1/internal/mtp-policy ins…