Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/code/issue5/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve machine-generated raw logs byte-for-byte; their manifest hashes are evidence.
results/raw/** -whitespace
13 changes: 13 additions & 0 deletions src/code/issue5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.venv/
.uv-bootstrap/
__pycache__/
.pytest_cache/
.ruff_cache/
*.egg-info/
dist/
build/
configs/*.env
results/runtime/
results/logs/
results/derived/fixture/
results/.last_*_run_id
20 changes: 20 additions & 0 deletions src/code/issue5/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.PHONY: test lint check model-example analyze-fixture

test:
uv run python -m pytest -q

lint:
uv run ruff check kvbreak benchmarks tests
uv run ruff format --check kvbreak benchmarks tests

check: lint test
bash -n orchestration/*.sh orchestration/gates/*.sh
git diff --check

model-example:
uv run python -m kvbreak.cli model --full-prefill-ms 800 --suffix-prefill-ms 360 \
--hit-rate 0.5 --kv-bytes 1073741824 --metadata-ms 2 --startup-ms 1 --restore-ms 20

analyze-fixture:
uv run python -m kvbreak.cli analyze --input tests/fixtures/requests.jsonl \
--measurement-s 60 --output-dir results/derived/fixture
162 changes: 162 additions & 0 deletions src/code/issue5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Issue 5:KV Cache 以存代算临界带宽与 TCP/RDMA 对照实验

本目录实现 `src/test/issue5/README.md` 的可复现实验骨架:从实测 prefill 分解计算临界带宽,使用 Mooncake Store + vLLM `MooncakeStoreConnector` 在相同模型、提示词、命中率和 TTFT SLO 下对照 TCP 与 RDMA,并把原始记录、失败请求、运行配置和文件哈希一起保留下来。

> 证据边界:仓库当前包含已通过本地测试的模型、负载统计、证据判定和双机编排代码;不包含伪造的 A40/Mooncake 性能数字。`report/RESULTS.md` 中只有完成远端 Gate 并校验 manifest 后,才允许填写 `MEASURED` 结果。2026-08-01 已完成 Pythia 14M 单请求 peermem GPUDirect RDMA 兼容性闭环,但 Qwen 7B 正式性能矩阵仍为 `UNKNOWN`。

## 方法概览

串行临界条件为:

```text
Tcache(B) = Tmeta + Tstartup + KVbytes / B + Trestore + Tprefill(suffix)
Bcrit = KVbytes / (Tprefill(full) - Tprefill(suffix) - Tmeta - Tstartup - Trestore)
```

只有 `B > Bcrit` 才判为有收益;分母不大于零时报告 `never_profitable`,命中率为零时报告 `no_hit`,不会用无穷大或零掩盖边界情况。重叠执行使用数值求根的独立模型,模型的所有输入都写入结果。

KV 容量采用实际模型的 GQA 维度:

```text
KVbytes = 2 × layers × cached_tokens × num_kv_heads × head_dim × bytes_per_element
```

## 本地快速验证

需要 Python 3.10–3.12 和 [uv](https://docs.astral.sh/uv/):

```bash
cd src/code/issue5
uv sync --extra dev
make check
make model-example
make analyze-fixture
```

示例模型命令只演示计算路径,不是实测结论。分析器会把 timeout/error/OOM 保留在分母中,QPM 只按成功完成请求计算。

## 双机实验

建议拓扑:GPU 节点运行 Writer/Reader/recompute,独立节点持有 Mooncake Store。两个 vLLM 实例的 `global_segment_size` 都是 `0`,避免把 requester 本机内存误当成共享远端 Store。

1. 在 Linux/CUDA 节点解析 `requirements-remote.in`,记录最终锁文件和包版本。
2. 复制并检查 `configs/tcp.env.example`、`configs/rdma.env.example`;不要把密钥写进配置。
3. 两台机器分别运行 `orchestration/preflight.sh`,保存完整输出。
4. 运行原始链路 Gate;RDMA 使用 `mlx5_0` 与明确 GID index,保留 perftest stdout/stderr。
5. 启动 Store Owner,再分别启动 Writer、Reader 或 recompute。三种角色共享同一模型 revision、tokenizer revision、RoPE、TP、dtype 和随机种子。
6. 按 `run_matrix.sh` 的固定 Gate 顺序逐步放大;任一 Gate 失败就停止,不把部分数据升级成正式结果。
7. 收集 `results/raw`,生成 manifest 哈希,再用 `kvbreak analyze` 生成派生表。

脚本只停止本项目 PID 文件指向、且工作目录仍位于本项目下的进程,不使用全局进程名清理。

Store 与 requester 的 RoCE IP 不再写死在版本库。启动 vLLM 前显式传入:

```bash
export MOONCAKE_STORE_HOST=<Store RoCE IP>
export MOONCAKE_REQUESTER_HOST=<Requester RoCE IP>
export ISSUE5_WRITER_GPUS=0,1
export ISSUE5_READER_GPUS=0,1
export ISSUE5_RECOMPUTE_GPUS=0,1
```

RDMA 还必须显式选择 GPU 内存注册路径,不允许继承 Mooncake 的版本默认值:

```bash
# 开放内核模块、且所选 GPU 的 CUDA attribute 124 为 1 时使用
export ISSUE5_RDMA_GPU_REGISTRATION=dmabuf

# 管理员已加载 nvidia-peermem 或 nv_peer_mem 时使用
export ISSUE5_RDMA_GPU_REGISTRATION=peermem
```

`start_vllm.sh` 会在进程启动前查询所选物理 GPU 的
`GPU_DIRECT_RDMA_SUPPORTED`(CUDA attribute 116)和 `DMA_BUF_SUPPORTED`
(attribute 124),并检查 peer-memory 模块是否已经加载。校验通过后,脚本才把
`dmabuf` 映射为 `WITH_NVIDIA_PEERMEM=0`、把 `peermem` 映射为
`WITH_NVIDIA_PEERMEM=1`。未设置、拼写错误或能力不满足都会 fail closed。

远端模型不在默认路径,或目标 vLLM 需要改变加载策略时,可显式覆盖:

```bash
export ISSUE5_MODEL_CONFIG=$PWD/results/runtime/model.resolved.yaml
export ISSUE5_SAFETENSORS_LOAD_STRATEGY=eager # lazy|eager|prefetch
export ISSUE5_ENFORCE_EAGER=1 # 0|1
```

这些值会进入环境 provenance 白名单。每次启动前,脚本会生成
`results/runtime/vllm-<role>-<protocol>.environment.json`,脱敏记录最终
`CUDA_VISIBLE_DEVICES`、注册策略与派生的 `WITH_NVIDIA_PEERMEM`;未在白名单内的
token/secret 不会落盘。`start_vllm.sh` 对 vLLM 0.23 使用
`--hf-overrides` 传递 RoPE 配置,并拒绝未知加载策略,避免参数静默失效。

脚本会从只含 transport 差异的模板生成 `results/runtime/*.resolved.json`,使 TCP/RDMA 的拓扑完全一致,同时把解析后的配置纳入运行证据。Writer、Reader 和 recompute 共用 GPU 时必须串行执行,不允许服务重叠。

## 远端命中与 RDMA 证明标准

- 远端命中同时要求:cached token 非零、Store GET 字节达到理论 KV 字节的 80%、Reader 单次使用或经过可证明的本地驱逐环、Reader 本地 cache hit 为零、输出摘要匹配 recompute 对照。
- `protocol=rdma` 只是配置意图。只有 RDMA 传输已加载、无 fallback、RDMA 计数器增量与 payload 相符,且 TCP payload 增量足够小,才升级成 `RDMA_HOST_STAGING`。
- 只有进一步证明 CUDA 内存注册且没有 host staging,才标为 `GPUDIRECT_RDMA`。否则绝不宣传 GPUDirect。

请求完成后还可把 recompute JSONL、Reader JSONL 与 vLLM Prometheus 快照交叉验证:

```bash
uv run kvbreak verify-remote-hit \
--baseline results/raw/<run>/node1/requests/recompute.jsonl \
--reader results/raw/<run>/node1/requests/reader.jsonl \
--metrics results/raw/<run>/node1/mooncake/vllm-reader-prometheus.txt \
--model-name <served-model-name> \
--output results/raw/<run>/remote-hit-verdict.json
```

只有请求集合一致、全部成功、输出摘要逐项一致、Store GET 字节和外部命中 token
至少达到期望值的 80%,且 GET 失败键为零时,命令才以成功状态退出。

Mooncake 0.3.12 发布源码的实际默认值是 `WITH_NVIDIA_PEERMEM=true`,即 GPU 指针
默认进入依赖 `nvidia-peermem`/`nv_peer_mem` 的 legacy `ibv_reg_mr` 路径;运行时显式
设置 `WITH_NVIDIA_PEERMEM=0` 才选择 DMA-BUF 导出与 `ibv_reg_dmabuf_mr`。若日志出现
`Failed to register memory: Bad address`、`register_buffer failed` 或
`AddressNotRegistered`,即使裸 `ib_write_bw` 通过,也必须停止 RDMA 端到端 Gate。
`MC_STORE_MEMCPY=1` 控制本地端点 memcpy,不能当成可靠的 GPU→Host staging 回退。

2026-08-01 的双节点诊断进一步确认:Requester node1 的所有 A40 均报告 attribute
116=1、attribute 124=0,而当时 peer-memory 模块存在于磁盘但未加载;强制 DMA-BUF
后,旧的 `Bad address` 消失,Mooncake 明确报告该 GPU 不支持 DMA-BUF。Store node3
的开放 NVIDIA 内核模块下 attribute 124=1。这组根因证据见
`results/raw/remote-20260801/`。

在用户明确授权后,node1 加载了版本与 kernel/driver 匹配的
`nvidia_peermem` 580.95.05,并使用空闲 GPU 0 重跑单请求 Writer/Reader smoke。
Writer 63/63 PUT keys、Reader 63/63 GET keys 全部成功,Reader 读取的
3,096,576 bytes 与理论 KV payload 一致,1008/1008 cached tokens 被外部前缀
cache 命中,输出摘要与 fresh recompute 一致。显式 peermem 注册、成功的
CUDA-backed KV arena 注册、禁用的 endpoint memcpy/host staging、运行时
`nvidia_peermem` use count 与方向匹配的 RDMA 计数器增量共同将这次功能试验升级为
`GPUDIRECT_RDMA_COMPATIBILITY`。原始证据、机器判定和 manifest 见
`results/raw/remote-20260801-peermem-smoke/`。该结论只证明单请求 Pythia 14M
兼容性,不是 Qwen 7B、长上下文、QPM、SLO 或 RDMA-over-TCP 加速结论。

## 目录

```text
kvbreak/ 临界模型、统计、证据分级、分析和 manifest 校验
benchmarks/ 确定性工作负载、开放环负载统计、Store payload 校验
configs/ 锁定模型与只改变 transport 字段的 TCP/RDMA 配置
orchestration/ 预检、部署、启停、网络基准和分阶段 Gate
tests/ 单元测试、失败样本和安全性回归测试
report/ 正式结果模板与限制
```

## 上游与版本

- vLLM:`>=0.23,<0.24`
- Mooncake Transfer Engine:`>=0.3.12,<0.4`
- 模型:`Qwen/Qwen2.5-7B-Instruct`,配置锁到 upstream verified commit `a09a354`;正式 run manifest 还应记录解析后的完整 SHA。

这些范围只用于目标 Linux/CUDA 环境解析;macOS 本地锁文件不冒充 GPU 节点依赖锁。

接口与数据路径依据:[Mooncake Store 设计文档](https://github.com/kvcache-ai/Mooncake/blob/main/docs/source/design/mooncake-store.md)、
[vLLM 0.23 Mooncake Store Connector 指南](https://docs.vllm.ai/en/v0.23.0/features/mooncake_store_connector_usage/)、
[Mooncake v0.3.12 环境变量源码](https://github.com/kvcache-ai/Mooncake/blob/v0.3.12/mooncake-common/src/environ.cpp#L126)、
[Mooncake v0.3.12 RDMA 注册实现](https://github.com/kvcache-ai/Mooncake/blob/v0.3.12/mooncake-transfer-engine/src/transport/rdma_transport/rdma_context.cpp)、
[DMA-BUF 引入 PR #2164](https://github.com/kvcache-ai/Mooncake/pull/2164) 与
[恢复 legacy 默认值 PR #2192](https://github.com/kvcache-ai/Mooncake/pull/2192)。
1 change: 1 addition & 0 deletions src/code/issue5/benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Benchmark entry points for issue 5."""
91 changes: 91 additions & 0 deletions src/code/issue5/benchmarks/loadgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from __future__ import annotations

import math
from dataclasses import dataclass

import numpy as np


@dataclass(frozen=True)
class RequestTiming:
request_id: str
scheduled_at_s: float
sent_at_s: float
first_token_at_s: float | None
completed_at_s: float
success: bool

@property
def ttft_ms(self) -> float | None:
if self.first_token_at_s is None:
return None
return (self.first_token_at_s - self.sent_at_s) * 1_000


@dataclass(frozen=True)
class LoadPointSummary:
successful_requests: int
total_requests: int
error_rate: float
median_ttft_ms: float
p95_ttft_ms: float
actual_qpm: float
max_inflight: int
compliant: bool


def arrival_schedule(*, offered_qps: float, count: int, start_s: float = 0) -> list[float]:
"""Return fixed open-loop arrival times; completions never delay later sends."""
if offered_qps <= 0:
raise ValueError("offered_qps must be positive")
if count < 0:
raise ValueError("count must be non-negative")
interval = 1 / offered_qps
return [start_s + index * interval for index in range(count)]


def max_inflight(timings: list[RequestTiming]) -> int:
events: list[tuple[float, int]] = []
for timing in timings:
if timing.completed_at_s < timing.sent_at_s:
raise ValueError("completion cannot precede send")
events.extend(((timing.sent_at_s, 1), (timing.completed_at_s, -1)))
# Count a request sent exactly when another completes as overlapping.
events.sort(key=lambda item: (item[0], -item[1]))
current = peak = 0
for _, delta in events:
current += delta
peak = max(peak, current)
return peak


def summarize_load_point(
timings: list[RequestTiming],
*,
measurement_s: float,
slo_ms: float,
max_error_rate: float = 0.01,
) -> LoadPointSummary:
if measurement_s <= 0 or slo_ms <= 0:
raise ValueError("measurement_s and slo_ms must be positive")
if not 0 <= max_error_rate <= 1:
raise ValueError("max_error_rate must be in [0, 1]")
total = len(timings)
successes = [timing for timing in timings if timing.success and timing.ttft_ms is not None]
values = [float(timing.ttft_ms) for timing in successes if timing.ttft_ms is not None]
successful = len(successes)
error_rate = (total - successful) / total if total else 1.0
median = float(np.median(values)) if values else math.inf
p95 = float(np.percentile(values, 95)) if values else math.inf
actual_qpm = successful * 60 / measurement_s
compliant = bool(values) and p95 <= slo_ms and error_rate <= max_error_rate
return LoadPointSummary(
successful_requests=successful,
total_requests=total,
error_rate=error_rate,
median_ttft_ms=median,
p95_ttft_ms=p95,
actual_qpm=actual_qpm,
max_inflight=max_inflight(timings),
compliant=compliant,
)
25 changes: 25 additions & 0 deletions src/code/issue5/benchmarks/mooncake_store_bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import annotations

import hashlib
import random


def make_payload(size_bytes: int, *, seed: int) -> bytes:
if size_bytes < 0:
raise ValueError("size_bytes must be non-negative")
return random.Random(seed).randbytes(size_bytes)


def verify_payload(expected: bytes, actual: bytes) -> bool:
return (
len(expected) == len(actual)
and hashlib.sha256(expected).digest() == hashlib.sha256(actual).digest()
)


def bandwidth_bytes_per_s(*, completed_bytes: int, wall_s: float) -> float:
if completed_bytes < 0:
raise ValueError("completed_bytes must be non-negative")
if wall_s <= 0:
raise ValueError("wall_s must be positive")
return completed_bytes / wall_s
Loading