Сервер NextStat
Самостоятельный GPU API вывода
Автономный HTTP-сервер, предоставляющий движок статистического вывода NextStat через JSON REST API. Разверните на GPU-узле и делитесь со всей лабораторией — без настройки CUDA для каждого пользователя, без проблем с Python-окружениями.
Архитектура
┌─────────────┐ HTTP/JSON ┌──────────────────────┐
│ Python │ ◄──────────────► │ nextstat-server │
│ thin client │ /v1/fit │ (axum + tokio) │
│ (httpx) │ /v1/ranking │ │
└─────────────┘ /v1/batch/* │ ┌─────────────────┐ │
/v1/models │ │ Model Cache │ │
Jupyter / CI / /v1/health │ │ (LRU, SHA-256) │ │
Airflow / curl │ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ GPU Mutex Queue │ │
│ │ CUDA / Metal │ │
│ └─────────────────┘ │
└──────────────────────┘Быстрый старт
# Сборка сервера (только CPU)
cargo build --release -p ns-server
# Запуск на порту 3742 (по умолчанию)
./target/release/nextstat-server
# С CUDA GPU
cargo build --release -p ns-server --features cuda
./target/release/nextstat-server --gpu cuda --port 8080Python-клиент
Модуль nextstat.remote — тонкий клиент на чистом Python. Требуется только httpx — без Rust, без CUDA, без компилируемых расширений.
pip install httpx
import nextstat.remote as remote
client = remote.connect("http://gpu-server:3742")
# Один фит
result = client.fit(workspace_json)
print(f"μ̂ = {result.bestfit[0]:.4f} ± {result.uncertainties[0]:.4f}")
# Кеш модели: загрузить один раз, затем делать фиты без повторного парсинга
model_id = client.upload_model(workspace_json, name="my-analysis")
result = client.fit(model_id=model_id) # примерно в 4 раза быстрее
# Батч-фит: несколько workspace в одном запросе
batch = client.batch_fit([ws1, ws2, ws3])
for r in batch.results:
print(r.nll if r else "ошибка")
# Батч-тои: GPU-ускоренные псевдоэксперименты
toys = client.batch_toys(workspace_json, n_toys=10_000, seed=42)
print(f"{toys.n_converged}/{toys.n_toys} сошлось за {toys.wall_time_s:.1f}с")
# Ранкинг
ranking = client.ranking(workspace_json)
for e in ranking.entries:
print(f" {e.name}: Δμ = {e.delta_mu_up:+.4f} / {e.delta_mu_down:+.4f}")Справка API
POST /v1/fit
Фит максимального правдоподобия. Автоопределение форматов pyhf и HS3 workspace. Передайте model_id вместо workspace для использования кешированной модели.
# Запрос
{
"workspace": { ... }, // pyhf или HS3 (или опустите, если передан model_id)
"model_id": "abc...", // опционально, из POST /v1/models
"gpu": true // опционально, по умолчанию true
}
# Ответ
{
"parameter_names": ["mu", "bkg_norm"],
"poi_index": 0,
"bestfit": [1.17, -0.03],
"uncertainties": [1.00, 0.97],
"nll": 6.908,
"twice_nll": 13.816,
"converged": true,
"n_iter": 4,
"n_fev": 6,
"n_gev": 10,
"covariance": [1.00, -0.66, -0.66, 0.95],
"device": "cuda",
"wall_time_s": 0.002
}POST /v1/ranking
Ранкинг влияния мешающих параметров, отсортированный по |Δμ| по убыванию. Поддерживает model_id. Metal GPU пока не поддерживает ранкинг — сервер возвращает HTTP 400 с описательной ошибкой. Используйте CUDA или CPU для ранкинга.
# Запрос
{
"workspace": { ... }, // или "model_id": "abc..."
"gpu": true
}
# Ответ
{
"entries": [
{
"name": "bkg_norm",
"delta_mu_up": -0.71,
"delta_mu_down": 0.68,
"pull": -0.026,
"constraint": 0.975
}
],
"device": "cuda",
"wall_time_s": 0.001
}POST /v1/batch/fit
Фит до 100 workspace'ов в одном запросе.
# Запрос
{ "workspaces": [{ ... }, { ... }], "gpu": true }
# Ответ
{
"results": [
{ "index": 0, "bestfit": [...], "nll": 6.9, "converged": true, ... },
{ "index": 1, "error": "ошибка парсинга: ..." }
],
"device": "cpu",
"wall_time_s": 0.005
}POST /v1/batch/toys
GPU-ускоренный батч-фиттинг тоев (CUDA, Metal или CPU (Rayon)).
# Запрос
{
"workspace": { ... },
"params": [1.0, 0.0], // опционально, по умолчанию init модели
"n_toys": 1000, // по умолчанию 1000, максимум 100000
"seed": 42,
"gpu": true
}
# Ответ
{
"n_toys": 1000,
"n_converged": 998,
"results": [{ "bestfit": [...], "nll": 7.1, "converged": true, "n_iter": 12 }, ...],
"device": "cuda",
"wall_time_s": 0.8
}POST /v1/models
Загрузка workspace в кеш моделей. Возвращает SHA-256 model_id для использования в fit/ranking.
# Запрос
{ "workspace": { ... }, "name": "my-analysis" }
# Ответ
{ "model_id": "1fb0d639...", "n_params": 250, "n_channels": 5, "cached": true }GET /v1/models
Список всех кешированных моделей с метаданными.
DELETE /v1/models/:id
Удаление модели из кеша.
GET /v1/health
{
"status": "ok",
"version": "0.9.0",
"uptime_s": 3600.5,
"device": "cuda",
"inflight": 2,
"total_requests": 1547,
"cached_models": 3
}Опции сервера
| Флаг | По умолчанию | Описание |
|---|---|---|
| --port | 3742 | Порт прослушивания |
| --host | 0.0.0.0 | Адрес привязки |
| --gpu | none | "cuda" или "metal" (CPU если опущен) |
| --threads | 0 (auto) | Количество CPU-потоков для не-GPU нагрузок |
Сериализация GPU
Сервер принимает параллельные HTTP-соединения, но сериализует GPU-доступ черезtokio::sync::Mutex. Только один фит или ранкинг выполняется на GPU одновременно; остальные в очереди. HTTP-ответы неблокирующие — сервер может принимать новые запросы пока GPU-задача выполняется.
Выполнение инструментов (агентный интерфейс)
Сервер зеркалирует nextstat.tools через HTTP, позволяя агентам загружать определения инструментов и выполнять их без импорта Python.
GET /v1/tools/schema
Возвращает OpenAI-совместимые определения инструментов. Оболочка: schema_version = "nextstat.tool_schema.v1".
POST /v1/tools/execute
// Запрос
{
"name": "nextstat_fit",
"arguments": {
"workspace_json": "{...}",
"execution": { "deterministic": true }
}
}
// Ответ (всегда оболочка результата инструмента)
{
"schema_version": "nextstat.tool_result.v1",
"ok": true,
"result": { ... },
"error": null,
"meta": { "tool_name": "nextstat_fit", "nextstat_version": "..." }
}Заметки о детерминизме
ns_compute::EvalMode является общим для процесса. Чтобы избежать гонок, сервер сериализует запросы вывода за глобальной блокировкой вычислений. Параметр на уровне запроса execution.eval_mode безопасен (нет утечек между запросами), но общая пропускная способность ниже (один запрос вывода за раз).
Политика GPU: если execution.deterministic=true (по умолчанию), инструменты выполняются на CPU. Если execution.deterministic=false и сервер запущен с --gpu cuda|metal, некоторые инструменты могут использовать GPU.
Python-клиент (транспорт: сервер)
from nextstat.tools import get_toolkit, execute_tool
server_url = "http://127.0.0.1:3742"
tools = get_toolkit(transport="server", server_url=server_url)
out = execute_tool(
"nextstat_fit",
{"workspace_json": "...", "execution": {"deterministic": True}},
transport="server",
server_url=server_url,
)
# server_url можно задать через переменную окружения NEXTSTAT_SERVER_URL
# fallback_to_local=False отключает локальный fallbackБезопасность / политика ввода
Серверный режим не предоставляет инструменты загрузки файлов (например, чтение ROOT-файлов из произвольных путей) через /v1/tools/*. Если вам нужна загрузка ROOT для демо-агента, делайте это на клиенте и отправляйте производные данные на сервер.
Развёртывание
Docker
# Сборка CPU
docker build -t nextstat-server -f crates/ns-server/Dockerfile .
docker run -p 3742:3742 nextstat-server
# Сборка CUDA
docker build --build-arg FEATURES=cuda -t nextstat-server:cuda -f crates/ns-server/Dockerfile .
docker run -p 3742:3742 --gpus all nextstat-server:cuda --gpu cudaHelm (Kubernetes)
helm install nextstat-server crates/ns-server/helm/nextstat-server \
--set server.gpu=cuda \
--set gpu.enabled=true \
--set image.tag=0.9.0Systemd
[Unit]
Description=Сервер NextStat для вывода на GPU
After=network.target
[Service]
ExecStart=/usr/local/bin/nextstat-server --gpu cuda --port 3742
Restart=always
Environment=RUST_LOG=info
[Install]
WantedBy=multi-user.target