ข้ามไปที่เนื้อหา

Benchmarks

เฟรมเวิร์กการวัดประสิทธิภาพ (benchmark) วัดประสิทธิภาพของ inference engine ด้วยเทสต์แบบมาตรฐานที่ทำซ้ำได้ มี benchmark ในตัวสำหรับ latency และ throughput, ตัวรัน suite สำหรับการรันแบบ batch และรองรับ benchmark กำหนดเอง

ภาพรวม

OpenJarvis มาพร้อม benchmark สองตัว:

Benchmark Registry Key วัดอะไร
Latency latency Latency ของการอนุมานต่อการเรียก (mean, p50, p95, min, max)
Throughput throughput Throughput เป็น token ต่อวินาที

BaseBenchmark ABC

Benchmark ทั้งหมด implement abstract base class BaseBenchmark

from abc import ABC, abstractmethod
from openjarvis.bench._stubs import BenchmarkResult
from openjarvis.engine._stubs import InferenceEngine

class BaseBenchmark(ABC):

    @property
    @abstractmethod
    def name(self) -> str:
        """Short identifier for this benchmark."""

    @property
    @abstractmethod
    def description(self) -> str:
        """Human-readable description of what this benchmark measures."""

    @abstractmethod
    def run(
        self,
        engine: InferenceEngine,
        model: str,
        *,
        num_samples: int = 10,
    ) -> BenchmarkResult:
        """Execute the benchmark and return results."""

BenchmarkResult

การรัน benchmark แต่ละครั้งสร้าง BenchmarkResult:

ฟิลด์ ชนิด คำอธิบาย
benchmark_name str ชื่อของ benchmark
model str โมเดลที่ใช้
engine str Engine backend ที่ใช้
metrics dict[str, float] คู่ key-value ของ metric ที่วัดได้
metadata dict[str, Any] Metadata เพิ่มเติม
samples int จำนวน sample ที่รัน
errors int จำนวน error ที่พบ

Benchmark ในตัว

Latency Benchmark

วัด latency ของการอนุมานต่อการเรียก ด้วย prompt สั้นๆ ที่ตายตัว แต่ละ sample ส่ง prompt ง่ายๆ ไปยัง engine แล้ววัดเวลาแบบ wall-clock

Prompt ที่ใช้: benchmark นี้หมุนเวียนใช้ prompt สำเร็จรูปสั้นๆ ("Hello", "What is 2+2?", "Explain gravity in one sentence") เพื่อให้ความหลากหลายของ input คงที่ในทุกการรัน

Metric ที่สร้าง:

Metric คำอธิบาย
mean_latency Latency เฉลี่ยของ sample ที่สำเร็จทั้งหมด
p50_latency Latency มัธยฐาน (percentile ที่ 50)
p95_latency Latency percentile ที่ 95 (ประสิทธิภาพส่วนหาง)
min_latency การเรียกที่เร็วที่สุด
max_latency การเรียกที่ช้าที่สุด

ตัวอย่างผลลัพธ์:

latency (10 samples, 0 errors)
  mean_latency: 0.2345
  p50_latency:  0.2100
  p95_latency:  0.3800
  min_latency:  0.1500
  max_latency:  0.4200

Throughput Benchmark

วัด throughput ของการอนุมานเป็น token ต่อวินาที แต่ละ sample ส่ง prompt ที่ยาว กว่า ("Write a short paragraph about artificial intelligence") แล้ววัดทั้ง เวลาที่ใช้และจำนวน completion token ที่สร้างขึ้น

Metric ที่สร้าง:

Metric คำอธิบาย
tokens_per_second Completion token ทั้งหมด / เวลาทั้งหมด
total_tokens Completion token ทั้งหมดรวมทุก sample
total_time_seconds เวลาแบบ wall-clock ทั้งหมดรวมทุก sample

ตัวอย่างผลลัพธ์:

throughput (10 samples, 0 errors)
  tokens_per_second:  45.6789
  total_tokens:       1250.0000
  total_time_seconds: 27.3600

การตีความผลลัพธ์

Metric ของ Latency

  • mean_latency: เวลาตอบสนองเฉลี่ย ใช้สำหรับเปรียบเทียบประสิทธิภาพทั่วไป
  • p50_latency (มัธยฐาน): เวลาตอบสนองทั่วไป ได้รับผลกระทบจาก outlier น้อยกว่าค่าเฉลี่ย
  • p95_latency: เวลาตอบสนองที่แย่ที่สุดของ 95% ของคำขอ สำคัญต่อประสบการณ์ผู้ใช้ -- หากค่านี้สูงเกินไป ผู้ใช้บางส่วนจะเจอความล่าช้าที่สังเกตได้
  • min/max_latency: การเรียกเดี่ยวที่ดีที่สุดและแย่ที่สุด ช่องว่างระหว่าง min กับ max ที่มากบ่งชี้ว่าประสิทธิภาพไม่สม่ำเสมอ

สิ่งที่ควรสังเกต

การตั้งค่าที่สุขภาพดีจะมี p95 / p50 < 2 หาก p95 สูงกว่ามัธยฐานมาก ให้ตรวจสอบว่า engine กำลังเจอปัญหาการแย่งชิงทรัพยากร, thermal throttling หรือความกดดันด้านหน่วยความจำหรือไม่

Metric ของ Throughput

  • tokens_per_second: ตัวชี้วัด throughput หลัก ยิ่งสูงยิ่งดี ช่วงทั่วไป:
    • เฉพาะ CPU: 5-20 token/วินาที
    • GPU ระดับผู้ใช้ทั่วไป (RTX 3060-4090): 30-100 token/วินาที
    • GPU ระดับ datacenter (A100, H100): 100-500+ token/วินาที
  • total_tokens / total_time: ข้อมูลดิบเบื้องหลังการคำนวณ throughput มีประโยชน์สำหรับตรวจสอบว่า engine กำลังสร้างผลลัพธ์ที่มีความหมาย (ไม่ใช่การตอบกลับว่างเปล่า)

BenchmarkSuite

คลาส BenchmarkSuite รัน benchmark หลายตัวพร้อมกัน และมี utility สำหรับรวม ผลลัพธ์และ serialize

from openjarvis.bench._stubs import BenchmarkSuite
from openjarvis.bench.latency import LatencyBenchmark
from openjarvis.bench.throughput import ThroughputBenchmark

suite = BenchmarkSuite([LatencyBenchmark(), ThroughputBenchmark()])

# Run all benchmarks
results = suite.run_all(engine, model, num_samples=20)

# Serialize to JSONL (one JSON object per line)
jsonl = suite.to_jsonl(results)

# Get a summary dict
summary = suite.summary(results)

Method

Method คืนค่า คำอธิบาย
run_all(engine, model, num_samples=10) list[BenchmarkResult] รัน benchmark ทั้งหมดตามลำดับ
to_jsonl(results) str Serialize ผลลัพธ์เป็นรูปแบบ JSONL
summary(results) dict[str, Any] สร้าง dictionary สรุปผล

รูปแบบ JSONL

แต่ละบรรทัดในผลลัพธ์ JSONL เป็น object JSON:

{"benchmark_name": "latency", "model": "qwen3:8b", "engine": "ollama", "metrics": {"mean_latency": 0.234, "p50_latency": 0.21, "p95_latency": 0.38, "min_latency": 0.15, "max_latency": 0.42}, "metadata": {}, "samples": 10, "errors": 0}
{"benchmark_name": "throughput", "model": "qwen3:8b", "engine": "ollama", "metrics": {"tokens_per_second": 45.67, "total_tokens": 1250.0, "total_time_seconds": 27.36}, "metadata": {}, "samples": 10, "errors": 0}

รูปแบบ Summary

{
  "benchmark_count": 2,
  "benchmarks": [
    {
      "name": "latency",
      "model": "qwen3:8b",
      "engine": "ollama",
      "metrics": {"mean_latency": 0.234, ...},
      "samples": 10,
      "errors": 0
    },
    {
      "name": "throughput",
      "model": "qwen3:8b",
      "engine": "ollama",
      "metrics": {"tokens_per_second": 45.67, ...},
      "samples": 10,
      "errors": 0
    }
  ]
}

การใช้งานผ่าน CLI

# Run all benchmarks with default settings (10 samples)
jarvis bench run

# Run with more samples for better statistical accuracy
jarvis bench run -n 50

# Run only the latency benchmark
jarvis bench run -b latency

# Run only the throughput benchmark with 20 samples
jarvis bench run -b throughput -n 20

# Specify model and engine
jarvis bench run -m qwen3:8b -e ollama

# Output JSON summary to stdout
jarvis bench run --json

# Write JSONL results to a file
jarvis bench run -o results.jsonl

# Combine options
jarvis bench run -b latency -n 100 -m qwen3:8b --json -o latency.jsonl
ตัวเลือก ชนิด ค่าเริ่มต้น คำอธิบาย
-m, --model MODEL string อัตโนมัติ โมเดลที่จะ benchmark
-e, --engine ENGINE string อัตโนมัติ Engine backend
-n, --samples N int 10 จำนวน sample ต่อ benchmark
-b, --benchmark NAME string ทั้งหมด Benchmark เฉพาะที่จะรัน (latency หรือ throughput)
-o, --output PATH path ไม่มี เขียนผลลัพธ์ JSONL ลงไฟล์
--json flag ปิด แสดงผลลัพธ์สรุปแบบ JSON ทาง stdout

เพิ่ม Benchmark กำหนดเอง

สร้าง benchmark กำหนดเองได้ด้วยการ subclass BaseBenchmark และลงทะเบียนกับ BenchmarkRegistry

ขั้นตอนที่ 1: Implement Benchmark

import time
from openjarvis.bench._stubs import BaseBenchmark, BenchmarkResult
from openjarvis.core.registry import BenchmarkRegistry
from openjarvis.core.types import Message, Role
from openjarvis.engine._stubs import InferenceEngine


class ContextLengthBenchmark(BaseBenchmark):
    """Measures how latency scales with input length."""

    @property
    def name(self) -> str:
        return "context_length"

    @property
    def description(self) -> str:
        return "Measures latency scaling with increasing input length"

    def run(
        self,
        engine: InferenceEngine,
        model: str,
        *,
        num_samples: int = 10,
    ) -> BenchmarkResult:
        latencies = {}
        errors = 0

        for length in [100, 500, 1000, 2000]:
            prompt = "x " * length
            messages = [Message(role=Role.USER, content=prompt)]

            t0 = time.time()
            try:
                engine.generate(messages, model=model)
                latencies[f"latency_{length}_tokens"] = time.time() - t0
            except Exception:
                errors += 1

        return BenchmarkResult(
            benchmark_name=self.name,
            model=model,
            engine=engine.engine_id,
            metrics=latencies,
            samples=len(latencies),
            errors=errors,
        )

ขั้นตอนที่ 2: ลงทะเบียน Benchmark

ใช้รูปแบบ ensure_registered() เพื่อให้รอดจากการล้าง registry ในเทสต์:

def ensure_registered() -> None:
    """Register the benchmark if not already present."""
    if not BenchmarkRegistry.contains("context_length"):
        BenchmarkRegistry.register_value("context_length", ContextLengthBenchmark)

หรือใช้ decorator ตอนกำหนดคลาสก็ได้:

@BenchmarkRegistry.register("context_length")
class ContextLengthBenchmark(BaseBenchmark):
    ...

รูปแบบ ensure_registered()

ฟังก์ชัน ensure_registered() เป็นที่นิยมกว่า decorator สำหรับโมดูล benchmark เพราะรอดจากการล้าง registry ระหว่างทดสอบได้ benchmark ในตัวทั้ง latency และ throughput ใช้รูปแบบนี้ทั้งคู่ คำสั่ง CLI ของ benchmark เรียก ensure_registered() ก่อนค้นหา benchmark เสมอ

ขั้นตอนที่ 3: ใช้ Benchmark ของคุณ

เมื่อลงทะเบียนแล้ว benchmark ของคุณจะใช้งานได้ผ่าน CLI:

jarvis bench run -b context_length

และผ่าน BenchmarkSuite:

from openjarvis.core.registry import BenchmarkRegistry

bench_cls = BenchmarkRegistry.get("context_length")
bench = bench_cls()
result = bench.run(engine, model, num_samples=5)