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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ telefuser/
| `FlashVSR` | VSR | Streaming video super-resolution via [examples/flashvsr/README.md](examples/flashvsr/README.md) |
| `SwiftVR` | Causal video restoration | Single-GPU BF16 offline and direct streaming restoration via [examples/swiftvr/README.md](examples/swiftvr/README.md) |

### Robotics and Action Models

| Pipeline | Task | Notes |
|----------|------|-------|
| `LingBot-VLA v2` | Vision-language-action inference | RobotWin observations to normalized canonical action chunks via the direct SDK or native structured service; see [examples/lingbot_vla_v2/README.md](examples/lingbot_vla_v2/README.md) |

### Video Generation

| Pipeline | Task | Notes |
Expand Down Expand Up @@ -277,6 +283,7 @@ See [examples/README.md](examples/README.md) for the example runner and baseline
- [docs/en/adding_new_model.md](docs/en/adding_new_model.md): integrating new models
- [docs/en/adding_new_example.md](docs/en/adding_new_example.md): authoring examples and pipeline contracts
- [docs/en/abot_world.md](docs/en/abot_world.md): ABot-World single-GPU interactive pipeline, controls, and tests
- [examples/lingbot_vla_v2/README.md](examples/lingbot_vla_v2/README.md): LingBot-VLA v2 inference, structured service, parity, and validation boundaries
- [examples/swiftvr/README.md](examples/swiftvr/README.md): SwiftVR checkpoint loading, streaming usage, performance, and acceleration options

## Known Limitations
Expand Down
49 changes: 46 additions & 3 deletions benchmarks/telefuser_aiperf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,52 @@ TELEFUSER_AIPERF_CONCURRENCY=2 \
```

Each terminal result is required to contain a finite `50x55` action chunk and the frozen structured result fields.
The adapter retains an action hash, bounds, dimensions, verification status, target inference time, and peak memory;
it does not copy full action arrays or Base64 cameras into AIPerf response records. This validates service execution
and normalized action structure, not physical robot control semantics.
The VLA adapter registers `vla_inference_time` and `vla_peak_memory` as AIPerf record metrics, so the normal AIPerf
artifact contains their per-request values and aggregated p50/p95/p99 summaries alongside request latency, throughput,
and success rate. It retains an action hash, bounds, dimensions, and verification status; it does not copy full action
arrays or Base64 cameras into AIPerf response records. This validates service execution and normalized action structure,
not physical robot control semantics.

For a target-process RSS and per-GPU process-memory artifact, pass the native service PID. This is an external bounded
sampler and does not change the TeleFuser service or any shared metric endpoint:

```bash
TELEFUSER_AIPERF_SERVICE_PID=<service-pid> \
bash benchmarks/telefuser_aiperf/scripts/run_vla_structured_bench.sh
```

The resource summary is written to
`artifacts/telefuser_aiperf/vla_structured/resource_summary.json` and includes sample count, RSS mean/p50/p95/p99,
and per-GPU process-memory mean/p50/p95/p99. It is intentionally separate from AIPerf server metrics because RSS is
an observer-side process-tree fact, while GPU telemetry remains target-side Prometheus data.

The checked-in AIPerf configuration defaults to two excluded warmup requests and 20 measured requests. For a longer
distribution, override the request count and concurrency without editing the configuration:

```bash
TELEFUSER_AIPERF_REQUESTS=100 \
TELEFUSER_AIPERF_CONCURRENCY=2 \
bash benchmarks/telefuser_aiperf/scripts/run_vla_structured_bench.sh
```

## VLA GPU Parallelism Assessment

The current VLA policy validates `world_size == 1`, so this assessment is deliberately read-only and does not enable
FSDP, tensor parallelism, or pipeline parallelism. It reports visible GPU capacity, current free memory, checkpoint
size, and how many complete replicas fit using a measured resident-memory estimate:

```bash
.venv-vla/bin/python tools/validation/inspect_lingbot_vla_v2_gpu_plan.py \
--model-root /hhb-data/aigc/model_zoo/lingbot/lingbot-vla-v2-6b \
--replica-memory-mib 13302 \
--output work_dirs/vla_gpu_plan.json
```

On the validated four-H100 host, the report showed four visible 80 GB GPUs and four estimated complete replicas at
the 13,302 MiB measured process-memory baseline. This supports request-level multi-replica service capacity; it is
not evidence that one model replica can be split across GPUs. Any future FSDP or tensor-parallel implementation must
be introduced behind the VLA pipeline boundary and re-run the frozen preprocessing, velocity, action, and HTTP
contract tests before it is considered equivalent.

## LingBot-World v2 Streaming

Expand Down
169 changes: 169 additions & 0 deletions benchmarks/telefuser_aiperf/scripts/run_vla_structured_bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""Run the VLA AIPerf workload and sample target process resources."""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import threading
import time
from pathlib import Path
from statistics import fmean
from typing import Any

import psutil

_MIB = 1024.0 * 1024.0


def _percentile(values: list[float], fraction: float) -> float | None:
if not values:
return None
ordered = sorted(values)
position = (len(ordered) - 1) * fraction
lower = int(position)
upper = min(lower + 1, len(ordered) - 1)
return ordered[lower] + (ordered[upper] - ordered[lower]) * (position - lower)


def _summary(values: list[float]) -> dict[str, float | int | None]:
return {
"count": len(values),
"mean": fmean(values) if values else None,
"p50": _percentile(values, 0.50),
"p95": _percentile(values, 0.95),
"p99": _percentile(values, 0.99),
"max": max(values) if values else None,
}


def _process_tree(root_pid: int) -> tuple[set[int], float]:
try:
root = psutil.Process(root_pid)
processes = [root, *root.children(recursive=True)]
except psutil.Error:
return set(), 0.0
process_ids: set[int] = set()
rss_bytes = 0
for process in processes:
try:
process_ids.add(process.pid)
rss_bytes += process.memory_info().rss
except psutil.Error:
continue
return process_ids, rss_bytes / _MIB


def _gpu_memory(process_ids: set[int]) -> dict[str, float]:
if not process_ids:
return {}
try:
result = subprocess.run(
[
"nvidia-smi",
"--query-compute-apps=pid,gpu_uuid,used_gpu_memory",
"--format=csv,noheader,nounits",
],
check=True,
capture_output=True,
text=True,
timeout=5,
)
except (OSError, subprocess.SubprocessError):
return {}
memory: dict[str, float] = {}
for line in result.stdout.splitlines():
fields = [field.strip() for field in line.split(",")]
if len(fields) != 3:
continue
try:
pid = int(fields[0])
value = float(fields[2])
except ValueError:
continue
if pid in process_ids and value >= 0:
memory[fields[1]] = memory.get(fields[1], 0.0) + value
return memory


def _sample(root_pid: int, started_at: float) -> dict[str, Any]:
process_ids, rss = _process_tree(root_pid)
return {
"offset_seconds": time.perf_counter() - started_at,
"process_ids": sorted(process_ids),
"cpu_rss_mib": rss,
"gpu_memory_mib": _gpu_memory(process_ids),
}


def run(args: argparse.Namespace) -> int:
root = Path(__file__).resolve().parents[3]
python = os.environ.get("TELEFUSER_AIPERF_PYTHON", str(root / ".venv-aiperf/bin/python"))
command = [python, "-m", "telefuser_aiperf.cli", "profile", "--config", str(args.config)]
environment = os.environ.copy()
adapter_root = root / "benchmarks/telefuser_aiperf"
environment["PYTHONPATH"] = f"{adapter_root}{os.pathsep}{environment.get('PYTHONPATH', '')}".rstrip(os.pathsep)

started_at = time.perf_counter()
process = subprocess.Popen(command, cwd=root, env=environment)
samples: list[dict[str, Any]] = []
stop = threading.Event()

def sample_loop() -> None:
while not stop.is_set():
samples.append(_sample(args.service_pid, started_at))
stop.wait(args.sample_interval)

sampler = threading.Thread(target=sample_loop, name="vla-aiperf-resource-sampler", daemon=True)
sampler.start()
return_code = process.wait()
stop.set()
sampler.join(timeout=max(args.sample_interval * 2.0, 1.0))
samples.append(_sample(args.service_pid, started_at))

rss_values = [float(sample["cpu_rss_mib"]) for sample in samples]
gpu_values: dict[str, list[float]] = {}
for sample in samples:
for gpu, value in sample["gpu_memory_mib"].items():
gpu_values.setdefault(gpu, []).append(float(value))
report = {
"schema_version": 1,
"benchmark": "lingbot_vla_v2_aiperf_structured",
"aiperf_command": command,
"service_pid": args.service_pid,
"return_code": return_code,
"elapsed_seconds": time.perf_counter() - started_at,
"cpu_rss_mib": _summary(rss_values),
"gpu_process_memory_mib": {gpu: _summary(values) for gpu, values in sorted(gpu_values.items())},
"samples": samples[-args.max_samples :],
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2, sort_keys=True))
return return_code


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--config",
type=Path,
default=Path("benchmarks/telefuser_aiperf/configs/vla_structured_e2e.yaml"),
)
parser.add_argument("--service-pid", type=int, required=True)
parser.add_argument("--sample-interval", type=float, default=1.0)
parser.add_argument("--max-samples", type=int, default=600)
parser.add_argument(
"--output",
type=Path,
default=Path("artifacts/telefuser_aiperf/vla_structured/resource_summary.json"),
)
args = parser.parse_args()
if args.service_pid < 1 or args.sample_interval <= 0 or args.max_samples < 2:
parser.error("service PID must be positive, interval must be positive, and max-samples must be at least 2")
raise SystemExit(run(args))


if __name__ == "__main__":
main()
11 changes: 11 additions & 0 deletions benchmarks/telefuser_aiperf/scripts/run_vla_structured_bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,15 @@ if command -v curl >/dev/null 2>&1; then
fi

export PYTHONPATH="${ADAPTER_ROOT}${PYTHONPATH:+:${PYTHONPATH}}"
if [[ -n "${TELEFUSER_AIPERF_SERVICE_PID:-}" ]]; then
RESOURCE_PYTHON="${TELEFUSER_VLA_PYTHON:-${ROOT_DIR}/.venv-vla/bin/python}"
if [[ ! -x "${RESOURCE_PYTHON}" ]]; then
echo "The VLA resource sampler interpreter is unavailable: ${RESOURCE_PYTHON}" >&2
exit 1
fi
exec "${RESOURCE_PYTHON}" benchmarks/telefuser_aiperf/scripts/run_vla_structured_bench.py \
--config "${CONFIG_PATH}" \
--service-pid "${TELEFUSER_AIPERF_SERVICE_PID}" \
--output "${TELEFUSER_AIPERF_RESOURCE_OUTPUT:-artifacts/telefuser_aiperf/vla_structured/resource_summary.json}"
fi
exec "${AIPERF_PYTHON}" -m telefuser_aiperf.cli profile --config "${CONFIG_PATH}"
43 changes: 42 additions & 1 deletion benchmarks/telefuser_aiperf/telefuser_aiperf/vla_structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@
from urllib.parse import quote, urlsplit, urlunsplit

import orjson
from aiperf.common.exceptions import NotInitializedError
from aiperf.common.enums import MetricFlags, MetricSizeUnit, MetricTimeUnit
from aiperf.common.exceptions import NoMetricValue, NotInitializedError
from aiperf.common.models import (
BaseResponseData,
ErrorDetails,
InferenceServerResponse,
ParsedResponse,
ParsedResponseRecord,
RequestInfo,
RequestRecord,
TextResponse,
)
from aiperf.endpoints.base_endpoint import BaseEndpoint
from aiperf.metrics import BaseRecordMetric
from aiperf.metrics.metric_dicts import MetricRecordDict
from aiperf.plugin.schema.schemas import TransportMetadata
from aiperf.transports.aiohttp_transport import AioHttpTransport

Expand Down Expand Up @@ -60,6 +64,43 @@ class VlaActionResponseData(BaseResponseData):
peak_memory_mb: float | None = None


class VlaInferenceTimeMetric(BaseRecordMetric[float]):
"""Expose server-measured VLA inference time to AIPerf."""

tag = "vla_inference_time"
header = "VLA Inference Time"
short_header = "VLA Inference"
unit = MetricTimeUnit.SECONDS
display_unit = MetricTimeUnit.MILLISECONDS
display_order = 310
flags = MetricFlags.NONE

def _parse_record(self, record: ParsedResponseRecord, record_metrics: MetricRecordDict) -> float:
del record_metrics
for response in reversed(record.responses):
if isinstance(response.data, VlaActionResponseData) and response.data.inference_time_s is not None:
return response.data.inference_time_s
raise NoMetricValue("VLA inference time is not available in the structured response.")


class VlaPeakMemoryMetric(BaseRecordMetric[float]):
"""Expose server-measured peak accelerator memory to AIPerf."""

tag = "vla_peak_memory"
header = "VLA Peak Memory"
short_header = "VLA Peak Memory"
unit = MetricSizeUnit.MEGABYTES
display_order = 311
flags = MetricFlags.NONE

def _parse_record(self, record: ParsedResponseRecord, record_metrics: MetricRecordDict) -> float:
del record_metrics
for response in reversed(record.responses):
if isinstance(response.data, VlaActionResponseData) and response.data.peak_memory_mb is not None:
return response.data.peak_memory_mb
raise NoMetricValue("VLA peak memory is not available in the structured response.")


def _finite_number(value: Any, *, name: str, allow_none: bool = False) -> float | None:
if value is None and allow_none:
return None
Expand Down
5 changes: 5 additions & 0 deletions benchmarks/telefuser_aiperf/tests/test_vla_structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ def test_registration_uses_aiperf_endpoint_and_transport_plugins() -> None:
assert transport_class is TeleFuserStructuredHttpTransport
assert plugins.get_endpoint_metadata("telefuser_vla_structured").requires_polling is True

from aiperf.metrics.metric_registry import MetricRegistry

assert MetricRegistry.get_class("vla_inference_time").__name__ == "VlaInferenceTimeMetric"
assert MetricRegistry.get_class("vla_peak_memory").__name__ == "VlaPeakMemoryMetric"


def test_build_vla_payload_reuses_inline_image_for_three_cameras() -> None:
encoded = base64.b64encode(b"image bytes").decode()
Expand Down
3 changes: 3 additions & 0 deletions benchmarks/telefuser_aiperf/vla_structured_contract.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ metrics:
- request_latency
- request_throughput
- success_rate
- vla_inference_time
- vla_peak_memory
- server_metrics
artifacts:
config: benchmarks/telefuser_aiperf/configs/vla_structured_e2e.yaml
dataset: benchmarks/telefuser_aiperf/data/vla_structured.jsonl
resource_summary: artifacts/telefuser_aiperf/vla_structured/resource_summary.json
Loading
Loading