多服务器协调#
当您运行多个 LMCache 多进程(MP)服务器时,MP 协调器是一个独立服务,各服务器向其注册,从而为您提供覆盖所有运行中服务器的单一全局视图。每个 MP 服务器独立缓存;协调器将它们整合为一个统一管理的集群。
运行协调器#
协调器是一个 FastAPI 服务。使用以下命令启动它:
lmcache coordinator
预期的日志输出:
LMCache INFO: MP coordinator listening on http://0.0.0.0:9300
See lmcache coordinator for the full flag list. Equivalently, the
coordinator can be launched as a module with
python3 -m lmcache.v1.mp_coordinator, which accepts the same flags.
配置#
The coordinator is configured through CLI flags only; every flag left unset keeps the default below.
Flag |
默认 |
描述 |
|---|---|---|
|
|
HTTP服务器绑定的主机。 |
|
|
HTTP服务器绑定的端口。 |
|
|
超过此秒数未收到心跳后,服务器将从集群中移除。 |
|
|
Seconds between health-check sweeps that expire stale MP-server
registrations. |
|
|
L2 逐出清扫之间的秒数。 |
|
|
每个周期逐出的跟踪键的比例(按计数,范围为 0.0 到 1.0)。 |
|
|
当使用量达到配额的该比例时触发逐出(取值范围为 (0.0, 1.0],不含 0.0)。 |
|
|
每个 KV 块的令牌数:CacheBlend 匹配单元和用于将固定的 |
|
|
用于 pin 密钥解析的令牌哈希算法。必须与 MP 服务器的 |
|
off |
Index stored chunk content so |
|
|
Positions between CacheBlend match probes. |
|
|
HTTP 服务器在关闭空闲连接之前保持打开的秒数。必须大于 MP 服务器的心跳间隔(默认 |
|
off |
Skip OpenTelemetry metrics initialization. Metrics are on by default;
pass this flag and the local |
|
unset |
OTLP gRPC endpoint for metrics push mode. When unset, Prometheus pull
mode exposes |
Coordinator metrics export#
Metrics are enabled by default. Without an OTLP endpoint, Prometheus scrapes the coordinator's existing FastAPI port; no separate metrics server or port is created:
curl http://localhost:9300/metrics
Set --otlp-endpoint http://collector:4317 to push metrics to an
OpenTelemetry Collector instead. In OTLP push mode, and when
--disable-metrics is set, GET /metrics returns 404. This infrastructure
does not itself define coordinator business metrics; instruments register with
the shared OpenTelemetry provider as coordinator capabilities add them.
连接 MP 服务器#
当您通过 --coordinator-url 为 MP 服务器(lmcache server)指定协调器地址时,该服务器便会加入协调器。它在启动时注册、运行时持续发送心跳、关闭时注销——所有操作均在服务器自身的事件循环中完成。此功能为可选项:若未设置 URL,服务器的行为与之前完全相同。每个标志均可回退到对应的 LMCACHE_COORDINATOR_* 环境变量(在使用 Kubernetes Downward API 时非常便捷);显式标志优先于环境变量。
标志(在 MP 服务器上) |
环境回退 |
描述 |
|---|---|---|
|
|
协调器基础 URL,例如 |
|
|
协调器应通过此服务器访问的 IP(默认为服务器的外部 IP)。 |
|
|
心跳之间的秒数(必须为 |
|
|
Stream cache store/access/delete events to the coordinator, feeding the key directory (fleet-wide placement tracking) and, for L2 events, usage/quota tracking and eviction. |
|
|
Seconds between cache-event batch flushes (must be |
服务器在其稳定身份下注册(--instance-id / OTel service.instance.id);如果未传递该标志,服务器将在启动时生成一个随机的 UUID v4 并在该 UUID 下注册。
注册是尽力而为的:如果协调器无法访问,MP 服务器会记录警告,持续重试,并继续提供服务。启动时会拒绝格式错误的心跳间隔值。
HTTP 端点#
协调器的 HTTP 接口(基础 URL http://localhost:9300)分为:
车队成员资格和健康 -- 注册和存活 (
/instances,/healthz)。配额、使用情况和逐出 --
/quota组:每个租户的字节预算、使用情况核算,以及驱动全队逐出的使用事件摄取。缓存控制 --
/cache组:分派给命名服务器的缓存操作(热预取、固定/解固定和删除,更多功能即将推出)。CacheBlend fragment lookup --
POST /directory/blend-lookup: finds cached chunk content anywhere inside a query sequence, using the blend index derived from the key directory's token bindings. Server-to-coordinator only; not usually called by hand.
每个端点的文档如下。成功状态为 200,除非另有说明,{cache_salt} 使用 _default 哨兵表示空盐。数据类型位于 lmcache/v1/mp_coordinator/schemas.py。
车队成员资格和健康状态#
MP 服务器自动注册、心跳和注销(参见 Connecting MP servers);GET /instances 和 GET /healthz 是只读的操作员视图。
POST /instances#
注册(或重新注册)一个 MP 服务器。每个服务器在启动时会自动调用。
请求体:
字段 |
类型 |
描述 |
|---|---|---|
|
字符串 |
服务器 HTTP API 的 IP/主机地址;协调器拨打此地址,因此必须非空。 |
|
整数 |
服务器 HTTP API 的端口。 |
|
字符串 |
可选。服务器标识符;如果省略(或留空),协调器将生成一个并返回。 |
|
对象 |
可选。自由格式的 |
|
字符串 |
可选。服务器为点对点传输宣传的 URL;在不处于 P2P 时为空。 |
|
整数 |
可选(默认 |
响应 (200 OK):
{"instance_id": "server-1", "re_registered": false}
instance_id 是注册的 ID(当请求省略时生成的 ID);re_registered 在替换现有注册时为 true。
HTTP 状态码:
200: 已注册。422: 请求体未通过字段级验证(例如,空白的ip或超出范围的http_port)。
示例:
curl -s -X POST http://localhost:9300/instances \
-H 'Content-Type: application/json' \
-d '{"ip": "10.0.0.5", "http_port": 8080}'
# -> {"instance_id": "mp-3f2c9d...", "re_registered": false}
PUT /instances/{instance_id}/heartbeat#
记录存活心跳。在服务器运行时自动调用。
路径参数: instance_id — 记录心跳的实例。
响应 (200 OK):
{"instance_id": "server-1"}
HTTP 状态码:
200: 心跳已记录。404: 未知实例 — 调用者应通过POST /instances重新注册。
示例:
curl -s -X PUT http://localhost:9300/instances/server-1/heartbeat
# -> {"instance_id": "server-1"}
DELETE /instances/{instance_id}#
注销一个 MP 服务器。在关闭时自动调用。
路径参数: instance_id — 要注销的服务器。
响应: 204 No Content,返回一个空主体,无论实例是否已注册(幂等)。
HTTP 状态码:
204: 已注销(对于未知实例也会返回此状态)。
示例:
curl -s -X DELETE http://localhost:9300/instances/server-1 -o /dev/null -w '%{http_code}\n'
# -> 204
GET /instances#
列出每个注册的 MP 服务器。
响应 (200 OK):
{
"instances": [
{
"instance_id": "server-1",
"ip": "10.0.0.5",
"http_port": 8080,
"registration_time": 1719000000.0,
"metadata": {},
"p2p_advertised_url": "",
"mq_port": 0
}
]
}
每个条目报告服务器的 instance_id、协调器访问的 ip / http_port、墙钟 registration_time``(纪元秒)、注册时提供的任何 ``metadata,以及用于点对点传输的 p2p_advertised_url / mq_port``(当 P2P 被禁用时为空 / ``0)。
HTTP 状态码:
200: 已列出舰队(空舰队返回{\"instances\": []})。
示例:
curl -s http://localhost:9300/instances
GET /healthz#
协调器存活探测(用于 Kubernetes)。
响应 (200 OK):
{"status": "healthy"}
HTTP 状态码:
200: 协调器正常运行。
示例:
curl -s http://localhost:9300/healthz
# -> {"status": "healthy"}
配额、使用情况和逐出#
/quota 组拥有每个 cache_salt 字节预算、其背后的实时使用情况核算,以及驱动全队逐出的使用事件流。 (MP 服务器暴露了一个节点本地的 /quota,形状相同;这是其全队对应的版本。)使用 _default 作为路径参数以针对空字符串盐。
警告
请勿将 MP 服务器的节点本地 /quota API 与协调器的 API 一起使用。这两者是独立的、不同步的配额注册表,在 同一共享 L2 上强制执行逐出:服务器端的强制执行者(在服务器运行每个盐的逐出策略时处于活动状态)使用严格的白名单语义——任何缺失于 其自身 表中的盐都会被完全逐出——并且它永远无法看到在协调器上注册的配额,反之亦然。混合这两者会产生相互竞争的逐出决策:服务器可以删除协调器认为在配额内的数据(或在默认限制生效之前仍然豁免的数据)。每个部署选择一个所有者——在协调器管理的部署中,仅通过协调器的 /quota API 注册配额,并保持服务器的节点本地配额表不变。
没有显式配额的盐由注册表的 默认限制 (PUT /quota/config) 管控。启动时默认值未设置,未配额的盐 免于 逐出 — 配额存储在内存中,因此一个新启动的(重新)协调器在外部配额控制器重新同步之前有一个空的配额表,而免配额的默认值防止在这一窗口内大规模逐出未知租户。在重新注册每个盐的配额后,控制器将默认值设置为 0 — 这是启动严格允许列表强制执行的信号(所有未配额盐下的字节将在下一个周期变为可逐出):
# 1. re-register every tenant quota
curl -s -X PUT http://localhost:9300/quota/user-a \
-H 'Content-Type: application/json' -d '{"limit_gb": 10.0}'
# ... one PUT per tenant ...
# 2. arm eviction of everything else
curl -s -X PUT http://localhost:9300/quota/config \
-H 'Content-Type: application/json' -d '{"default_limit_gb": 0}'
# -> {"default_limit_gb": 0.0}
When MP servers enable --coordinator-event-reporting, they stream cache
store, access, and delete events to the coordinator's
POST /events. Applied l2 batches also feed the quota side:
the coordinator aggregates per-cache_salt usage, enforces quotas, and
selects LRU keys to evict. Each batch carries the server's instance_id,
incarnation, and a monotonically increasing sequence number (seq)
scoped to that instance, so replays are deduplicated and lost batches are
detected.
Active eviction loop. Every --eviction-check-interval seconds, the
coordinator inspects per-salt usage against the registered quotas and,
for any salt over the trigger watermark, picks LRU victims and
dispatches a single DELETE /cache/objects to a uniformly random registered MP
server. Because all MP servers share the same backing L2 (e.g. one S3
bucket), one dispatch evicts the keys for the whole fleet. The MP
server's L2 adapter fires on_l2_keys_deleted listeners after the
delete completes; those listeners ship delete events back through
POST /events, which is what updates the coordinator's LRU +
per-salt totals. Dispatch failures or no-instances-registered fall
through to the next cycle — at-least-once semantics, safe because the
S3 delete is idempotent.
Cold start. The coordinator's trackers are in-memory and are built only from the cache-event stream, so after a restart per-salt usage starts at zero even though the bytes are still resident in L2. Quotas under-report until enough events accumulate. Unquota'd salts are exempt from eviction until the quota controller sets a default limit, so a cold coordinator cannot mass-evict — but it can under-evict, and an operator re-arming quotas right after a restart should expect the usage numbers to climb toward the true value rather than start at it.
PUT /quota/config / GET /quota/config#
设置/读取未明确配额条目的盐的默认限制。
请求体 (PUT):
字段 |
类型 |
描述 |
|---|---|---|
|
浮动或空值 |
|
|
字符串 |
可选(默认 |
响应 (200 OK):
{"default_limit_gb": 0.0}
示例:
curl -s http://localhost:9300/quota/config
# -> {"default_limit_gb": null} (boot state: unquota'd exempt)
curl -s -X PUT http://localhost:9300/quota/config \
-H 'Content-Type: application/json' -d '{"default_limit_gb": 0}'
# -> {"default_limit_gb": 0.0} (allowlist enforcement armed)
PUT /quota/{cache_salt}#
创建或更新租户的字节预算。
路径参数: cache_salt — 租户标识符 (_default 表示空盐)。
请求体:
字段 |
类型 |
描述 |
|---|---|---|
|
浮点数 |
以 GiB 为单位的字节预算;必须为 |
|
字符串 |
可选(默认 |
响应 (200 OK):
{"cache_salt": "user-a", "limit_gb": 10.0, "status": "ok"}
HTTP 状态码:
200: 已应用配额。400: 无效的限制(负值或非有限值)。422: 请求体未通过字段级验证。
示例:
curl -s -X PUT http://localhost:9300/quota/user-a \
-H 'Content-Type: application/json' \
-d '{"limit_gb": 10.0}'
# -> {"cache_salt": "user-a", "limit_gb": 10.0, "status": "ok"}
DELETE /quota/{cache_salt}#
删除盐的配额条目。任何仍然缓存的字节将在下一个逐出周期变为超出预算(有效限制降至 0)。
路径参数: cache_salt — 租户标识符 (_default 表示空盐)。
查询参数: tier — 可选(默认 l2);配额适用的缓存层级。
响应 (200 OK):
{"cache_salt": "user-a", "limit_gb": 0.0, "status": "removed"}
当未为盐注册配额时,status 为 "not_found"``(仍然是 ``200 OK)。
HTTP 状态码:
200: 已移除,或not_found如果不存在配额。
示例:
curl -s -X DELETE http://localhost:9300/quota/user-a
# -> {"cache_salt": "user-a", "limit_gb": 0.0, "status": "removed"}
GET /quota/{cache_salt}#
读取单个盐的配额和实时使用情况。
路径参数: cache_salt — 租户标识符 (_default 表示空盐)。
查询参数: tier — 可选(默认 l2)。
响应 (200 OK):
{"cache_salt": "user-a", "quota_limit_gb": 10.0, "quota_exists": true, "usage_gb": 0.001}
quota_limit_gb 是以 GiB 为单位的配置限制(当未设置配额时为 0.0),quota_exists 表示是否注册了显式配额,usage_gb 是当前的总使用量。此端点对于未知的 salt 永远不会返回 404。
HTTP 状态码:
200: 报告配额和使用情况。
示例:
curl -s http://localhost:9300/quota/user-a
# -> {"cache_salt": "user-a", "quota_limit_gb": 10.0, "quota_exists": true, "usage_gb": 0.001}
GET /quota#
列出总使用量和每个 salt 的详细信息。
查询参数: tier — 可选(默认 l2)。
响应 (200 OK):
{
"total_gb": 0.005,
"by_cache_salt": [
{"cache_salt": "user-a", "quota_limit_gb": 10.0, "quota_exists": true, "usage_gb": 0.001}
]
}
total_gb 是所有 salt 的总使用量(以 GiB 为单位);每个 by_cache_salt 条目具有与 GET /quota/{cache_salt} 响应相同的字段。
HTTP 状态码:
200: 报告的使用情况。
示例:
curl -s http://localhost:9300/quota
# -> {"total_gb": 0.005, "by_cache_salt": [...]}
Usage events arrive on the fleet cache-event stream
(POST /events); there is no separate quota ingestion
endpoint. See docs/design/v1/mp_coordinator/cache_events.md for the
batch format and routing semantics.
Key directory#
The /directory group is a read-only operator view of the fleet-wide key
directory: which keys are cached, where (instance / tier / backend), and what
token ids each chunk holds. The directory is eventually consistent soft
state built from the servers' cache-event stream -- every answer is a hint to
be validated at the owning server, never a guarantee.
GET /directory/keys#
List cached keys and their placements, one page at a time.
Query parameters:
Parameter |
默认 |
描述 |
|---|---|---|
|
|
Keep placements on this tier ( |
|
(empty) |
Keep placements reported by this MP server (empty keeps every instance). |
|
(empty) |
Keep placements on this backend, e.g. |
|
|
Matching keys to skip (pagination). |
|
|
Maximum keys to return (1 to 10000). |
响应 (200 OK):
{
"total": 2,
"keys": [
{
"key": {
"chunk_hash_hex": "aa12...",
"model_name": "meta-llama/Llama-3.1-8B-Instruct",
"kv_rank": 0,
"object_group_id": 0,
"cache_salt": ""
},
"placements": [
{
"instance_id": "server-1",
"incarnation": 1719000000,
"tier": "l1",
"backend": "dram",
"size_bytes": 1048576,
"shared": false
}
],
"num_tokens": 256
}
]
}
total counts every key with at least one placement matching the filters;
keys is the requested page of them, each with only its matching
placements. num_tokens reports how many token ids the directory knows for
the key's chunk (0 = unknown) -- fetch the actual tokens via
POST /directory/lookup, which exists precisely so listing pages stay
small. Pages of a changing directory may skip or repeat keys (snapshot
semantics).
HTTP 状态码:
200: page returned (an empty directory returns{"total": 0, "keys": []}).422: invalid parameter (negativeoffset,limitout of range, unknowntier).
示例:
# Everything on server-1's L1:
curl -s "http://localhost:9300/directory/keys?tier=l1&instance_id=server-1&limit=100"
POST /directory/lookup#
Resolve cache content to its placements and token ids. One endpoint, two forms -- supply exactly one:
keys form --
{"keys": [...]}: resolve keys you already have (e.g. fromGET /directory/keys).tokens form --
{"token_ids": [...], "model_name": ..., "world_size": ..., "cache_salt": ...}: resolve a request's tokens to the keys of its complete chunks (the same fan-out the pin APIs use).
重要
token_ids must be the request's whole token sequence from position
0, not one chunk's worth. Chunk hashes are prefix-chained -- each chunk's
key depends on every token before it -- so a mid-request slice resolves to
different (nonexistent) keys. Trailing tokens that do not fill a chunk are
ignored.
Request body (tokens form):
{
"token_ids": [15496, 11, 995, 314],
"model_name": "meta-llama/Llama-3.1-8B-Instruct",
"world_size": 1,
"cache_salt": ""
}
Request body (keys form):
{
"keys": [
{
"chunk_hash_hex": "aa12...",
"model_name": "meta-llama/Llama-3.1-8B-Instruct",
"kv_rank": 0,
"object_group_id": 0,
"cache_salt": ""
}
]
}
响应 (200 OK):
{
"chunks": 1,
"results": [
{
"key": {"chunk_hash_hex": "aa12...", "model_name": "...", "kv_rank": 0,
"object_group_id": 0, "cache_salt": ""},
"placements": [
{"instance_id": "server-1", "incarnation": 1770000000, "tier": "l1",
"backend": "dram", "size_bytes": 8388608, "shared": false}
],
"token_ids": [15496, 11, 995]
}
]
}
chunks is the number of complete chunks the tokens resolved to (keys form:
the number of keys requested); results has one entry per resolved key, in
request order (tokens form: chunks x the per-rank fan-out). placements
is empty for keys the directory does not know; token_ids is empty when the
directory has no tokens for the key's chunk (never stored with token reporting
on, or not yet re-reported after an event gap).
HTTP 状态码:
200: results returned.400: the token sequence exceeds the per-request cap, or a resolution parameter is invalid (e.g.model_namecontains@).422: neither or both forms supplied,model_namemissing withtoken_ids, or a key is malformed (e.g.chunk_hash_hexis not hex).
Examples:
# Tokens form -- where is this prompt cached, and what does each chunk hold?
curl -s -X POST http://localhost:9300/directory/lookup \
-H 'Content-Type: application/json' \
-d '{"token_ids": [15496, 11, 995], "model_name": "m", "world_size": 1}'
# Keys form -- keys taken from GET /directory/keys:
curl -s -X POST http://localhost:9300/directory/lookup \
-H 'Content-Type: application/json' \
-d '{"keys": [{"chunk_hash_hex": "aa12...", "model_name": "m", "kv_rank": 0}]}'
缓存控制#
/cache 组将缓存操作分派到命名的 MP 服务器。它涵盖 热预取、固定/解固定 和 删除;进一步的缓存控制操作将在此处作为端点记录。
热预取(从 L2 预加载 L1)。 在请求到达之前,用已知提示的 KV 预热一个 MP 服务器的 L1,以便第一个请求命中 L1,而不是支付 L2 内联获取的费用——当您知道工作负载即将路由到节点(流量转移、热共享系统提示)时,这非常有用。
您通过 token ids 描述内容——缓存使用的单位——而不是通过内部缓存键,您无法构造这些键(键是内容哈希加上每个排名的布局位图)。协调器将请求转发到指定的服务器,该服务器对令牌进行哈希,将它们扩展到节点的各个排名,从 L2 加载块到 L1,并 保留 它们,以便后续查找命中。提交返回一个 request_id;轮询状态端点直到 completed。预热不需要获取锁——轮询仅报告进度,并在加载完成后清除服务器端作业。
POST /cache/prefetches#
在一个指定的服务器上提交一个令牌序列的预热预取。
请求体:
字段 |
类型 |
描述 |
|---|---|---|
|
字符串 |
目标 MP 服务器;必须已注册。 |
|
字符串 |
目标的 L1 缓冲区布局大小的模型。 |
|
整数 |
世界大小( |
|
列表[int] |
完全 |
|
字符串 |
可选(默认 |
响应 (200 OK):
{"instance_id": "server-1", "request_id": "abc123", "chunks": 12, "status": "submitted"}
当序列短于一个块时,什么都不会被提交,request_id 为空:
{"instance_id": "server-1", "request_id": "", "chunks": 0, "status": "noop"}
request_id 是用于轮询的 ID;chunks 是提交的完整块的数量。
HTTP 状态码:
200:已提交(或如上所述的noop)。404: 未知 ``instance_id``(未注册)。502: 目标服务器无法访问或拒绝了提交。422: 请求体未通过字段级验证。
备注
单节点范围: 一个 instance_id 仅对该节点的分片进行预热。对于跨多个节点的模型,每个节点的实例需提交一个请求。
示例:
curl -s -X POST http://localhost:9300/cache/prefetches \
-H 'Content-Type: application/json' \
-d '{
"instance_id": "server-1",
"model_name": "Qwen/Qwen3-8B",
"world_size": 1,
"token_ids": [101, 102, 103, "..."],
"cache_salt": "user-a"
}'
# -> {"instance_id": "server-1", "request_id": "abc123", "chunks": 12, "status": "submitted"}
GET /cache/prefetches/{instance_id}/{request_id}#
轮询已提交的预热预取;响应将拥有服务器的状态及其代码逐字传递。
路径参数:
字段 |
类型 |
描述 |
|---|---|---|
|
字符串 |
提交预取的服务器。 |
|
字符串 |
由 |
响应 (200 OK) 在加载运行时:
{"status": "pending"}
…并且一旦完成:
{"status": "completed", "found_keys": 12, "total_keys": 12}
found_keys 的 total_keys 请求块是常驻的。
HTTP 状态码:
200: 状态报告(pending或completed)。404: 未知的instance_id,或来自服务器的未知request_id。502: 目标服务器无法访问。
示例:
curl -s http://localhost:9300/cache/prefetches/server-1/abc123
# -> {"status": "completed", "found_keys": 12, "total_keys": 12}
固定/解固定(保护缓存不被逐出)。 固定一个令牌序列的缓存,以便在解固定之前不会从 L2 中被逐出。协调器将令牌序列解析为其对象键 **本地**(无需 MP 服务器往返),并将其记录在 L2 逐出计划中(POST)或释放它们(DELETE),将固定的键排除在基于配额的逐出之外。L2 固定是全舰队范围的(按 cache_salt),因此没有指定目标实例。
本地解析要求协调器的 chunk_size 和 hash_algorithm``(请参见 `Configuration`_)与 MP 服务器的 ``--chunk-size / --hash-algorithm 匹配;否则解析的键将与存储的键不匹配,且固定不会保护任何内容。它还要求 MP 服务器以 --no-separate-object-groups 启动(协调器在单个对象组中解析键)。
POST /cache/pins#
在 L2 逐出计划中固定一个令牌序列的键。
请求体:
字段 |
类型 |
描述 |
|---|---|---|
|
字符串 |
用于解析键时使用的模型的扇出等级。 |
|
整数 |
世界大小( |
|
列表[int] |
完整块被固定的提示令牌;必须与存储的内容匹配。子块序列不固定任何内容( |
|
字符串 |
可选(默认 |
响应 (200 OK):
{"requested": 12, "affected": 12, "status": "pinned"}
requested 是解析的完整块数;affected 是固定的 L2 键的数量(块数乘以每个排名的分支因子)。
HTTP 状态码:
200: 已固定。400:token_ids超过每个请求的上限,或者cache_salt违反了其不变性。422: 请求体未通过字段级验证。
示例:
curl -s -X POST http://localhost:9300/cache/pins \
-H 'Content-Type: application/json' \
-d '{
"model_name": "Qwen/Qwen3-8B",
"world_size": 1,
"token_ids": [101, 102, 103, "..."],
"cache_salt": "user-a"
}'
# -> {"requested": 12, "affected": 12, "status": "pinned"}
备注
Requires event reporting. The coordinator can only exclude keys from
eviction for a salt it is tracking, which requires the MP servers started with
--coordinator-event-reporting (see Connecting MP servers).
DELETE /cache/pins#
从 L2 逐出计划中取消一个令牌序列的键。请求体与 POST /cache/pins 相同。响应与 pin 相似(affected 是取消固定的键的数量),status 为 "unpinned"。Pins 是按引用计数的:一个块被固定 N 次需要 N 次取消固定才能被逐出。
HTTP 状态码: 与 POST /cache/pins 相同。
示例:
curl -s -X DELETE http://localhost:9300/cache/pins \
-H 'Content-Type: application/json' \
-d '{
"model_name": "Qwen/Qwen3-8B",
"world_size": 1,
"token_ids": [101, 102, 103, "..."],
"cache_salt": "user-a"
}'
# -> {"requested": 12, "affected": 12, "status": "unpinned"}
删除(通过令牌序列移除缓存)。 在一个指定的服务器上删除令牌序列的缓存,通过令牌 ID 定址。协调器将令牌解析为本地对象键(如 pin),并向指定服务器发出单个键地址的 DELETE /cache/objects 请求,从请求的层级中移除它们。tier 字段选择层级:l1 仅删除指定服务器的 L1,l2 仅删除 L2,all 则删除两者。当层级包括 L2 时,协调器首先从删除集合中删除任何受 L2 pin 保护的键,除非设置了 force — 因此,任何被 pin 的键在删除操作中会保留在每个被触及的层级中;force 会删除它们并移除这些 pins。
POST /cache/delete#
在一个指定的服务器上删除一个令牌序列。
请求体: (model_name, world_size, token_ids, cache_salt) 以及 tier (l1 / l2 / all) 和 force (布尔值,默认 false)。当 force 为 true 时,锁定的键仍然会被删除(节点上的 L1 读/写锁和协调器的 L2 固定集)。
响应 (200 OK):
{"instance_id": "server-1", "requested": 12, "affected": 24, "skipped": 0, "status": "deleted"}
requested 是已解析的完整块的数量。 affected 和 skipped 是 作用于的各层的总计: affected 计算节点移除的 L1 键加上协调器移除的 L2 键,而 skipped 计算节点拒绝的 L1 键加上因 L2 钉住而保留的 L2 键(仅限非强制)。同时存在于两个层中的块(tier=all)会对两个计数都有贡献,因此 affected 可能高达 2 x requested x world_size。一个子块序列返回 status "noop"。
HTTP 状态码:
200: 已删除(或为noop)。404:没有服务器在instance_id下注册。502: 目标服务器无法访问或拒绝了删除请求。
示例:
curl -s -X POST http://localhost:9300/cache/delete \
-H 'Content-Type: application/json' \
-d '{
"instance_id": "server-1",
"model_name": "Qwen/Qwen3-8B",
"world_size": 1,
"token_ids": [101, 102, 103, "..."],
"cache_salt": "user-a",
"tier": "all",
"force": false
}'
# -> {"instance_id": "server-1", "requested": 12, "affected": 24, "skipped": 0, "status": "deleted"}
CacheBlend fragment lookup#
POST /directory/blend-lookup is the fragment counterpart to
/directory/lookup: the query need not be a prefix, and each match reports
where the content sits in the query and where it sat when stored, so the caller
can re-RoPE it.
It is served from the blend index, derived from the key directory's token
bindings — the coordinator learns content from the cache-event stream, not from
a separate publish call. Both feeds default to off and both are required: the
coordinator needs --enable-blend-lookup, and every MP server whose chunks
should be discoverable needs --coordinator-event-reporting (a server with a
coordinator URL but no event reporting warns at startup and matches locally
only). Matching is chunked at the coordinator's --chunk-size — which must
equal the MP servers' --chunk-size — probing every
--blend-probe-stride positions.
请求体:
字段 |
类型 |
描述 |
|---|---|---|
|
字符串 |
Query tokens packed as base64 little-endian |
响应 (200 OK):
{
"matches": [
{"chunk_hash": "ab12...", "old_st": 0, "cur_st": 512},
{"chunk_hash": "cd34...", "old_st": 256, "cur_st": 768}
]
}
chunk_hash is the chunk's content hash, which the caller expands to
per-rank object keys with its own model, salt, and world size; old_st is
its position in the stored sequence (re-RoPE source) and cur_st its
position in the query (re-RoPE target). Matches are sorted ascending by
cur_st, at most one per chunk, and may overlap — a caller that scatters
them resolves overlaps itself. A query shorter than one chunk, or a coordinator
without --enable-blend-lookup, returns {"matches": []}.
HTTP 状态码:
200: lookup completed (an empty match list is not an error).422:tokens_b64is not valid base64 or not a whole number ofuint32tokens.
Index counts are reported under the blend key of GET /directory/stats.