agentleFS
guoqingbaoguoqingbao/vllm.rs/.cursor/skills/test-model/SKILL.md

test-model

Skill345 starsChanged 6 months agoMITView on GitHub

Test LLM models served by xinfer for correctness, output quality, and performance. Use when the user asks to test, benchmark, validate, or verify models — either from a local folder path or HuggingFace model IDs. Supports all xinfer-compatible formats: BF16, FP8, MXFP4, NVFP4, GGUF, GPTQ, AWQ, ISQ, Dense, MoE, and Multimodal architectures.

2 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions2 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 2

    1. Pipelex/pipelex★ 949same name
    2. xybrid-ai/xybrid★ 517same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository2 other files it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 9 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the user provides a parent directory (not a single model), scan it to find testable models:

    line 24
  2. Distinctive:

    Use these rough heuristics for memory estimation (single-GPU, including KV cache overhead):

    line 118
  3. Very specific:

    If a model fits in one GPU's free memory, use --d <gpu_id> with the GPU that has the most free memory.

    line 135
  4. Very specific:

    If a model needs 2 GPUs, use --d <id1>,<id2> with the two GPUs with the most free memory.

    line 136
  5. Distinctive:

    If a model exceeds all available GPU memory, report it as skipped and move to the next.

    line 137
  6. Very specific:

    Verify the build succeeds (exit code 0).

    line 151
  7. Distinctive:

    If the build fails, check and fix compilation errors before proceeding.

    line 153
  8. Very specific:

    Create test_model.py in the project root with the following capabilities:

    line 160
  9. Very specific:

    Test both thinking=false and thinking=true modes

    line 164
  10. Distinctive:

    Check output quality: detect excessive 3-gram repetition, too-short responses

    line 167
  11. Distinctive:

    Always wait 3 seconds after killing to ensure GPU memory is released.

    line 192
  12. Very specific:

    Run the server in the background with RUST_BACKTRACE=1 for debugging.

    line 204
  13. Distinctive:

    If the server fails to start or the test script returns errors:

    line 221
  14. Very specific:

    Debug with unwrap: If the model crashes during inference, temporarily change guard.step() to guard.step().unwrap() in src/core/engine.rs to …

    line 234
  15. Distinctive:

    If a model cannot be fixed, record the failure reason and continue to the next model.

    line 236
  16. Distinctive:

    After all models are tested, produce a summary table:

    line 242

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Test Model — Validate and Benchmark LLM Models on xinfer

Phase 0: Gather Model List

Collect the models to test. The user provides one or both of:

InputFormatExample
Local folderAbsolute path to a directory containing model weights/data/models or /data/Qwen3.5-27B-FP8
HuggingFace IDsComma-separated model IDsAxionML/Qwen3.5-2B-NVFP4, Qwen/Qwen3-4B

Detecting models in a local folder

If the user provides a parent directory (not a single model), scan it to find testable models:

# List subdirectories that look like model folders
for d in /data/*/; do
  if [ -f "$d/config.json" ] || ls "$d"/*.gguf 2>/dev/null | head -1 >/dev/null; then
    echo "$d"
  fi
done

For each candidate directory, determine the model type by reading config.json:

import json, os, sys, glob

def detect_model(path):
    """Detect model type and quantization from a local directory."""
    config_path = os.path.join(path, "config.json")
    gguf_files = glob.glob(os.path.join(path, "*.gguf"))

    info = {"path": path, "name": os.path.basename(path.rstrip("/"))}

    if gguf_files:
        info["format"] = "gguf"
        info["gguf_file"] = os.path.basename(gguf_files[0])
        return info

    if not os.path.exists(config_path):
        return None

    cfg = json.load(open(config_path))
    arch = (cfg.get("architectures") or ["Unknown"])[0]

    supported = [
        "LlamaForCausalLM", "MistralForCausalLM", "Ministral3ForConditionalGeneration",
        "Qwen2ForCausalLM", "Qwen3ForCausalLM", "Qwen3MoeForCausalLM",
        "Qwen3_5ForCausalLM", "Qwen3_5MoeForCausalLM",
        "Qwen3_5ForConditionalGeneration", "Qwen3_5MoeForConditionalGeneration",
        "Qwen3NextForCausalLM",
        "Qwen3VLForConditionalGeneration",
        "Gemma3ForConditionalGeneration", "Gemma3ForCausalLM",
        "Gemma4ForCausalLM", "Gemma4ForConditionalGeneration",
        "Phi3ForCausalLM", "Phi4ForCausalLM",
        "Glm4ForCausalLM", "Glm4MoeForCausalLM",
    ]
    if arch not in supported:
        info["skip"] = f"Unsupported architecture: {arch}"
        return info

    info["arch"] = arch
    info["format"] = "safetensors"

    qcfg = cfg.get("quantization_config", {})
    qm = qcfg.get("quant_method", "")
    if qm in ("fp8", "modelopt", "compressed-tensors"):
        algo = qcfg.get("quant_algo", "")
        fmt = qcfg.get("format", "")
        if algo and ("nvfp4" in algo.lower() or "fp4" in algo.lower()):
            info["quant"] = "nvfp4"
        elif "nvfp4" in fmt.lower():
            info["quant"] = "nvfp4"
        elif "mxfp4" in fmt.lower():
            info["quant"] = "mxfp4"
        elif qm == "fp8":
            info["quant"] = "fp8"
        else:
            info["quant"] = qm
    elif qm in ("gptq", "awq"):
        info["quant"] = qm
    elif qm == "mxfp4":
        info["quant"] = "mxfp4"
    else:
        info["quant"] = "bf16"

    return info

Present the detected models to the user as a table and confirm before proceeding.


Phase 1: Estimate GPU Requirements and Detect Hardware

Detect available GPUs

nvidia-smi --query-gpu=index,name,memory.total,memory.free --format=csv,noheader,nounits

Parse the output to get gpu_id, name, total_mb, free_mb for each GPU.

Estimate model memory

Use these rough heuristics for memory estimation (single-GPU, including KV cache overhead):

FormatEstimate (GB)
BF16 / FP16params_B * 2.2
FP8params_B * 1.2
MXFP4 / NVFP4params_B * 0.8
GGUF Q4_K_Mparams_B * 0.7
GGUF Q3_K_Mparams_B * 0.55
GGUF Q2_Kparams_B * 0.45
MoE (A3B active)Use active params for compute, total params for weight memory

Extract parameter count from the model name when possible (e.g. Qwen3.5-27B → 27B). For MoE models with A3B in the name, the weight memory uses total params but fits better than dense.

GPU assignment rules

  1. If a model fits in one GPU's free memory, use --d <gpu_id> with the GPU that has the most free memory.
  2. If a model needs 2 GPUs, use --d <id1>,<id2> with the two GPUs with the most free memory.
  3. If a model exceeds all available GPU memory, report it as skipped and move to the next.
  4. For models explicitly specified as multi-GPU by the user, respect that.

Phase 2: Build the Project

Build using build.sh:

cd <project_root>
./build.sh --install --features cuda,nccl,flashinfer,cutlass

Verify the build succeeds (exit code 0). The Error: Must provide model_id or weight_path message after build is expected — it means the binary compiled correctly.

If the build fails, check and fix compilation errors before proceeding.

Important, if you build on CUDA with cargo build, make sure always build xinfer binaries.

Phase 3: Create the Test Script

Create test_model.py in the project root with the following capabilities:

  • Accept --port to specify the API server port
  • Accept --wait for server readiness timeout
  • Test both thinking=false and thinking=true modes
  • Send a prompt with at least 1024 input tokens and request at least 2048 output tokens
  • Measure end-to-end throughput (completion_tokens / total_time)
  • Check output quality: detect excessive 3-gram repetition, too-short responses
  • Report prompt tokens, completion tokens, time, throughput, and quality verdict
  • Print a summary table at the end

The prompt should be a substantive multi-topic question (algorithms, data structures, etc.) padded with context tokens to reach the 1k+ input requirement. Use max_tokens: 2048 and temperature: 0.7. Set request timeout to 300s.

For thinking mode, add "extra_body": {"thinking": true} to the payload.

Quality checks:

  • Response must be at least 100 characters
  • 3-gram repetition: flag if any trigram appears more than max(10, 5% of total trigrams) times

Phase 4: Test Each Model

For each model, execute this sequence:

Step 1: Kill previous instances

pkill -9 -f 'xinfer' 2>/dev/null
sleep 3

Always wait 3 seconds after killing to ensure GPU memory is released.

Step 2: Start the server

Build the server command based on model type:

Model sourceCommand pattern
Local safetensors./target/release/xinfer --w <path> --ui-server --d <gpus> --port 7000
Local GGUF./target/release/xinfer --w <dir> --f <file.gguf> --ui-server --d <gpus> --port 7000
HuggingFace ID./target/release/xinfer --m <hf_id> --ui-server --d <gpus> --port 7000

Run the server in the background with RUST_BACKTRACE=1 for debugging.

Step 3: Wait for server readiness

Poll GET /v1/models every 2-3 seconds until it returns HTTP 200, with a timeout of:

  • Small models (< 10B): 120s
  • Medium models (10-40B): 300s
  • Large models (> 40B) or HF downloads: 600s

Step 4: Run the test script

python3 test_model.py --port 7000

Step 5: Handle failures

If the server fails to start or the test script returns errors:

  1. Check server logs for panics or errors
  2. Common issues and fixes:
ErrorLikely causeFix
MLX-quantized models panicIncompatible NVFP4 packingSkip model; use modelopt/compressed-tensors variant
Unable to load ... projection weightsDeltaNet weights not detected as quantizedCheck is_weight_quantized in deltanet.rs
CUDA out of memoryModel too large for GPUTry with more GPUs or skip
Server starts but API times outModel too slow on prefillIncrease test timeout to 600s
failed to fill whole bufferRunner process crashedCheck runner logs, enable RUST_BACKTRACE=full
  1. Debug with unwrap: If the model crashes during inference, temporarily change guard.step() to guard.step().unwrap() in src/core/engine.rs to get a full stack trace. Revert after debugging.
  2. If a model cannot be fixed, record the failure reason and continue to the next model.

Phase 5: Summarize Results

After all models are tested, produce a summary table:

## Test Results

| # | Model | Format | GPUs | thinking=false | thinking=true | Quality |
|---|-------|--------|------|----------------|---------------|---------|
| 1 | Qwen3.5-27B-FP8 | FP8 | 1 | 1342 in / 2048 out, 42.2 tok/s | 1342 in / 2048 out, 42.2 tok/s | OK |
| 2 | ... | ... | ... | ... | ... | ... |

### Notes
- Model X: SKIPPED — reason
- Model Y: FAILED — error description

Include for each model:

  • Model name and quantization format
  • Number of GPUs used
  • Input/output token counts and throughput for both thinking modes
  • Quality verdict (OK / ISSUES / FAILED / SKIPPED)

Quick Reference

Key files

FilePurpose
test_model.pyOpenAI API test script (created by this skill)
src/core/engine.rsEngine loop; guard.step() for debug
src/models/layers/deltanet.rsDeltaNet layer; quantization detection
src/models/layers/linear.rsLinear layer loaders (FP8, MXFP4, NVFP4)
build.shBuild script (compiles xinfer)

Build features

Feature setWhen to use
cuda,nccl,flashinfer,cutlassSM80+ (Ampere/Ada/Hopper), recommended
cuda,nccl,flashattn,cutlassAlternative to flashinfer
cuda,ncclV100 (SM70), no flash attention
metalmacOS Apple Silicon

Server flags

FlagPurpose
--w <path>Local model weight directory
--f <file>GGUF filename within the weight directory
--m <hf_id>HuggingFace model ID (auto-downloads)
--d <ids>GPU device IDs (e.g. 0 or 0,1)
--port <n>API server port
--disable-prefix-cacheDisable prefix caching (on by default)
--ui-serverEnable built-in ChatGPT-like web UI
--isq <fmt>In-situ quantization (q2k, q3k, q4k, q5k, q6k, q8_0)
--kvcache-dtype <mode>KV cache quantization: fp8, turbo8, turbo4, turbo3

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.