xinfer-ai 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ # Multimodal Model Usage
2
+
3
+ This project supports vision-language models (Qwen3-VL dense/MoE, Gemma3, Mistral3-VL). The server exposes `/v1/chat/completions` with mixed text+image content and optional web UI.
4
+
5
+ ## Starting servers
6
+ - Qwen3-VL (CUDA):
7
+ ```bash
8
+ target/release/xinfer --m Qwen/Qwen3-VL-8B-Instruct --ui-server
9
+ ```
10
+ - Gemma3 (vision, No Flash attention support):
11
+ ```bash
12
+ xinfer --m google/gemma-3-4b-it --ui-server
13
+ ```
14
+ - Mistral3-VL (vision):
15
+ ```bash
16
+ target/release/xinfer --m mistralai/Ministral-3-8B-Reasoning --ui-server
17
+ ```
18
+
19
+ ## Request payloads (OpenAI-compatible)
20
+ - Text + image URL:
21
+ ```json
22
+ {
23
+ "model": "default",
24
+ "messages": [
25
+ {"role":"user","content":[
26
+ {"type":"text","text":"Describe this image"},
27
+ {"type":"image_url","image_url":"https://example.com/cat.png"}
28
+ ]}
29
+ ]
30
+ }
31
+ ```
32
+ - Text + base64 image: `{"type":"image_base64","image_base64":"data:image/png;base64,..."}`
33
+
34
+ ## Tips
35
+ - Use smaller `--max-model-len` on Metal if VRAM is tight; consider `--kv-fraction` on CUDA to reserve cache.
36
+ - For batch image inputs, keep concurrent images modest; too many images will increase prefill time.
37
+ - `--ui-server` opens the built-in chat UI for uploads; without it, send HTTP requests directly.***
@@ -0,0 +1,83 @@
1
+ # OpenCode + xInfer (OpenAI-compatible endpoint)
2
+
3
+ This guide connects OpenCode directly to xInfer using the built-in
4
+ OpenAI-compatible `/v1/chat_completions` API. No proxy required.
5
+
6
+ ```
7
+ OpenCode -> xInfer (OpenAI-compatible)
8
+ ```
9
+
10
+ ## 1) Start xInfer on port 8000
11
+
12
+ ```bash
13
+ # Rust
14
+ xinfer --m Qwen/Qwen3.5-35B-A3B-FP8 --server --d 0
15
+
16
+ # Different model
17
+ xinfer --m Qwen/Qwen3.5-27B-FP8 --d 0 --server
18
+
19
+ # Python
20
+ python3 -m xinfer.server --m Qwen/Qwen3-Coder-Next-FP8 --d 0,1
21
+ ```
22
+
23
+ ## 2) Configure OpenCode
24
+
25
+ Install opencode (CLI)
26
+
27
+ ```shell
28
+ curl -fsSL https://opencode.ai/install | bash
29
+ # Or install with npm
30
+ npm i -g opencode-ai
31
+ ```
32
+
33
+ Export config into `~/.config/opencode/config.json`
34
+
35
+
36
+ ```json
37
+ {
38
+ "$schema": "https://opencode.ai/config.json",
39
+ "provider": {
40
+ "xinfer": {
41
+ "npm": "@ai-sdk/openai-compatible",
42
+ "name": "xInfer Local",
43
+ "options": {
44
+ "baseURL": "http://localhost:8000/v1"
45
+ },
46
+ "models": {
47
+ "qwen3-coder": {
48
+ "name": "Qwen3 Coder"
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "model": "xinfer/qwen3-coder"
54
+ }
55
+ ```
56
+
57
+ Install Desktop OpenCode (optional)
58
+
59
+ ```shell
60
+ visit https://opencode.ai/download
61
+ ```
62
+
63
+ Connect to provider -> custom -> base URL (http://localhost:8000/v1) -> Empty key
64
+
65
+
66
+ ## 3) Run OpenCode
67
+
68
+ run opencode (CLI)
69
+
70
+ ```shell
71
+ opencode
72
+ ```
73
+
74
+ Or, run OpenCode desktop (choose configured custom provider)
75
+
76
+ ### Trouble shooting
77
+
78
+ 1. Use the chat logger to monitor detailed interactions between OpenCode and xInfer.
79
+
80
+ ```shell
81
+ # Log into files (in folder ./log)
82
+ export XINFER_CHAT_LOGGER=1
83
+ ```
@@ -0,0 +1,65 @@
1
+ # Performance Benchmarks
2
+
3
+ This document contains detailed performance benchmarks for xInfer across different hardware platforms.
4
+
5
+ ## 🚀 CUDA Performance (A100 40GB)
6
+
7
+ ### Single Request Decoding Speed
8
+
9
+ | Model | Format | Size | Decoding Speed |
10
+ |-------|--------|------|----------------|
11
+ | Llama-3.1-8B | ISQ (BF16→Q4K) | 8B | **90.19** tokens/s |
12
+ | DeepSeek-R1-Distill-Llama-8B | Q2_K | 8B | **94.47** tokens/s |
13
+ | DeepSeek-R1-0528-Qwen3-8B | Q4_K_M | 8B | **95** tokens/s |
14
+ | GLM-4-9B-0414 | Q4_K_M | 9B | **70.38** tokens/s |
15
+ | QwQ-32B | Q4_K_M | 32B | **35.69** tokens/s |
16
+ | **Qwen3-30B-A3B** | Q4_K_M | **30B (MoE)** | **75.91** tokens/s |
17
+
18
+ ## 🍎 Metal Performance (Apple Silicon M4)
19
+
20
+ > Test Configuration:
21
+ > - Models: Qwen3-0.6B (BF16), Qwen3-4B (Q4_K_M), Qwen3-8B (Q2_K)
22
+ > - Concurrent Requests: 1 - 128
23
+ > - Max Model Length: 512 - 2048
24
+ > - Max Output Tokens/Request: 512 - 2048
25
+
26
+ | Model | Batch Size | Output Tokens | Time (s) | Throughput (tokens/s) |
27
+ |-------|------------|---------------|----------|----------------------|
28
+ | Qwen3-0.6B (BF16) | 128 | 63,488 | 83.13s | **763.73** |
29
+ | Qwen3-0.6B (BF16) | 32 | 15,872 | 23.53s | **674.43** |
30
+ | Qwen3-0.6B (BF16) | 1 | 456 | 9.23s | 49.42 |
31
+ | Qwen3-4B (Q4_K_M) | 1 | 1,683 | 52.62s | 31.98 |
32
+ | Qwen3-8B (Q2_K) | 1 | 1,300 | 80.88s | 16.07 |
33
+
34
+ ## 📊 Performance Comparison
35
+
36
+ > Test Configuration:
37
+ > - Model: Qwen3-0.6B (BF16)
38
+ > - Concurrent Requests: 256
39
+ > - Max Model Length: 1024
40
+ > - Max Output Tokens/Request: 1024
41
+
42
+ | Inference Engine | Hardware | Tokens | Time (s) | Throughput (tokens/s) |
43
+ |------------------|----------|--------|----------|----------------------|
44
+ | vLLM (Reference) | RTX 4070 | 133,966 | 98.37 | 1,361.84 |
45
+ | Nano-vLLM (Reference) | RTX 4070 | 133,966 | 93.41 | 1,434.13 |
46
+ | **xInfer** | **A100** | 262,144 | 23.88 | **10,977.55** |
47
+ | Nano-vLLM | A100 | 262,144 | 34.22 | 7,660.26 |
48
+
49
+ ### Key Insights
50
+
51
+ - **40%+ faster** than Nano-vLLM on A100
52
+ - **7x faster** than reference implementations on consumer hardware
53
+ - Efficient memory management with quantized models
54
+
55
+ ## 🔧 Reproduce Benchmarks
56
+
57
+ See [python/ReadMe.md](../python/ReadMe.md) for reproducible benchmark steps.
58
+
59
+ ## Optimization Tips
60
+
61
+ 1. **Use KV Cache Quantization** (`--kvcache-dtype fp8|turbo4|turbo3`) for memory efficiency — turbo4 gives 3.7x compression with good quality
62
+ 2. **Enable Flashinfer / Flash Attention** (`flashinfer` or `flashattn` feature) for maximum CUDA performance
63
+ 3. **Prefix Cache** is enabled by default for multi-turn conversations
64
+ 4. **Tune `--kv-fraction`** to balance memory usage and batch size
65
+ 5. **Use PD Disaggregation** for long-context workloads to prevent decoding stalls
@@ -0,0 +1,79 @@
1
+ # Prefix Cache (KV Reuse)
2
+
3
+ Prefix cache lets xInfer reuse KV cache blocks from prior requests when a new
4
+ prompt shares a prefix. This accelerates consecutive requests with overlapping
5
+ history (for example, chat sessions that replay the same system + earlier turns).
6
+
7
+ ## How it works
8
+ - Finished sequences contribute full KV blocks to a global prefix cache.
9
+ - New requests find the longest cached prefix (block-aligned) and reuse those blocks.
10
+ - Remaining tokens are prefetched as usual, with KV writes continuing after the cached prefix.
11
+
12
+ Prefix cache is block-granular: only full KV blocks are reused. If the common
13
+ prefix ends mid-block, the tail of that block is recomputed. When a prompt is
14
+ fully cached at block boundaries, the last block is recomputed to ensure a
15
+ non-empty prefill step for correct sampling.
16
+
17
+ ## Flags
18
+ - Prefix cache is **enabled by default**. Use `--disable-prefix-cache` to turn it off.
19
+ - `--prefix-cache-max-tokens <N>`: cap cache size in tokens (rounded down to block size).
20
+
21
+ If `--prefix-cache-max-tokens` is not set, defaults are:
22
+ - Normal mode: ~50% of GPU KV blocks
23
+ - PD server: ~75% of GPU KV blocks
24
+ - PD client: ~35% of GPU KV blocks
25
+
26
+ ## Hybrid Mamba Snapshot Stride
27
+ For hybrid Mamba models (for example Qwen3.5), prefix reuse also needs a
28
+ compatible Mamba snapshot at the matched boundary.
29
+
30
+ Use environment variable `XINFER_MAMBA_SNAPSHOT_STRIDE_BLOCKS` to control
31
+ sparse snapshot capture during decode (larger stride side usefull for limited GPU memory):
32
+ - Default: `1` blocks
33
+ - Minimum valid value: `1` (capture every block)
34
+ - Effective snapshot boundary in tokens: `block_size * stride`
35
+
36
+ Example with default `block_size=64` and stride `8`:
37
+ - Decode snapshot boundary is every `512` tokens.
38
+ - Effective hybrid prefix reuse is aligned to the nearest captured boundary.
39
+
40
+ This setting only sparsifies decode-time snapshot capture. Prompt/prefill
41
+ snapshot capture remains dense.
42
+
43
+ ## Notes
44
+ - Prefix cache uses the same KV memory pool as active sequences. A larger cache
45
+ reduces the maximum number of concurrent tokens available for new requests.
46
+ - Cached KV reuse is automatic; no `session_id` is required.
47
+ - Sliding window attention limits how much cached context is effectively used.
48
+
49
+ ## Inspecting cache hits
50
+
51
+ Chat completion responses include the prefix-cache hit count under
52
+ `usage.prompt_tokens_details.cached_tokens` (OpenAI extension). The field is
53
+ omitted when no hits occurred, so existing single-turn responses keep their
54
+ shape:
55
+
56
+ ```json
57
+ "usage": {
58
+ "prompt_tokens": 499,
59
+ "completion_tokens": 16,
60
+ "total_tokens": 515,
61
+ "prompt_tokens_details": { "cached_tokens": 480 }
62
+ }
63
+ ```
64
+
65
+ In Python (offline batch), call `engine.get_num_cached_tokens_for_seq(seq_id)`
66
+ on the `seq_id` returned in each `GenerationOutput`.
67
+
68
+ For models that emit `<think>…</think>` reasoning blocks, responses also
69
+ include `usage.completion_tokens_details.reasoning_tokens` so clients can
70
+ attribute completion cost across reasoning vs final-answer output:
71
+
72
+ ```json
73
+ "usage": {
74
+ "prompt_tokens": 12,
75
+ "completion_tokens": 256,
76
+ "total_tokens": 268,
77
+ "completion_tokens_details": { "reasoning_tokens": 192 }
78
+ }
79
+ ```
@@ -0,0 +1,119 @@
1
+ # Rust crate usage
2
+
3
+ This crate exposes a Rust-facing API for loading models, running generation, and optionally running
4
+ an OpenAI-compatible service without changing the existing project structure.
5
+
6
+ ## Add dependency
7
+
8
+ ```toml
9
+ [dependencies]
10
+ xinfer = { git = "https://github.com/guoqingbao/xinfer.git", rev = "1377fa9" }
11
+
12
+ [features]
13
+ cuda = ["xinfer/cuda"]
14
+ ```
15
+
16
+ Use the same Cargo features you would use for the CLI (`cuda`, `metal`, `nccl`, etc.).
17
+
18
+ ## Direct generation (text)
19
+
20
+ ```rust
21
+ use xinfer::api::{EngineBuilder, ModelRepo};
22
+ use xinfer::server::{ChatMessage, MessageContentType};
23
+ use xinfer::utils::{config::SamplingParams, log_throughput};
24
+
25
+ fn main() -> anyhow::Result<()> {
26
+ let mut engine =
27
+ EngineBuilder::new(ModelRepo::ModelID(("google/gemma-3-4b-it", None))).build()?;
28
+
29
+ let messages = vec![ChatMessage {
30
+ role: "user".to_string(),
31
+ content: MessageContentType::PureText("Say hello from the Rust API.".to_string()),
32
+ }];
33
+
34
+ let params = SamplingParams::default();
35
+ let output = engine.generate(params, messages)?;
36
+ println!("\n\n{}", output.decode_output);
37
+
38
+ log_throughput(&vec![output]);
39
+ }
40
+ ```
41
+
42
+ ## Multimodal request (URL or base64)
43
+
44
+ ```rust
45
+ use xinfer::api::{EngineBuilder, ModelRepo};
46
+ use xinfer::server::{ChatMessage, MessageContent, MessageContentType};
47
+ use xinfer::utils::config::SamplingParams;
48
+
49
+ fn main() -> candle_core::Result<()> {
50
+ let mut engine = EngineBuilder::new(ModelRepo::ModelID((
51
+ "Qwen/Qwen3-VL-8B-Instruct".to_string(),
52
+ None,
53
+ )))
54
+ .build()?;
55
+
56
+ let messages = vec![ChatMessage {
57
+ role: "user".to_string(),
58
+ content: MessageContentType::Multi(vec![
59
+ MessageContent::Text {
60
+ text: "Describe this image:".to_string(),
61
+ },
62
+ MessageContent::ImageUrl {
63
+ image_url: "https://example.com/cat.png".to_string(),
64
+ },
65
+ ]),
66
+ }];
67
+
68
+ let params = SamplingParams::default();
69
+ let output = engine.generate(params, messages)?;
70
+ println!("{}", output.decode_output);
71
+
72
+ Ok(())
73
+ }
74
+ ```
75
+
76
+ ## Serve API
77
+
78
+ ```rust
79
+ use xinfer::api::{EngineBuilder, ModelRepo};
80
+
81
+ fn main() -> candle_core::Result<()> {
82
+ let mut engine = EngineBuilder::new(ModelRepo::ModelID((
83
+ "Qwen/Qwen3-0.6B".to_string(),
84
+ None,
85
+ )))
86
+ .build()?;
87
+
88
+ engine.start_server(8000, true, false)?;
89
+ Ok(())
90
+ }
91
+ ```
92
+
93
+ ## Multi-rank / multi-GPU
94
+
95
+ Provide `device_ids` with `with_multirank` (e.g. `"0,1"`) along with the same CUDA/NCCL features
96
+ you use for the CLI. The Rust API reuses the same engine and scheduler path.
97
+
98
+ ```rust
99
+ use xinfer::api::{EngineBuilder, ModelRepo};
100
+
101
+ fn main() -> candle_core::Result<()> {
102
+ let mut engine = EngineBuilder::new(ModelRepo::ModelFile(vec![
103
+ "/path/Qwen3-VL-8B-Instruct-GGUF-Q4_KM.gguf".to_string(),
104
+ ]))
105
+ .with_multirank("0,1")?
106
+ .build()?;
107
+
108
+ engine.start_server(8000, true, true)?;
109
+ Ok(())
110
+ }
111
+ ```
112
+ ## Command to run
113
+
114
+ [Reference Rust demo](/example/rust-demo/)
115
+
116
+ ```shell
117
+ # add `nccl` feature for multirank inference
118
+ cargo run --release --features cuda -- ARGS
119
+ ```
@@ -0,0 +1,104 @@
1
+ # Model Testing (AI-Assisted)
2
+
3
+ xInfer ships with a **Cursor Agent Skill** that automates the process of testing LLM models for correctness, output quality, and inference performance. It handles GPU detection, model loading, API testing, and result summarization.
4
+
5
+ ## Prerequisites
6
+
7
+ - [Cursor IDE](https://cursor.sh/) with Agent mode enabled (for other Agents, mention the skill file manually)
8
+ - The xInfer repository cloned locally
9
+ - One or more NVIDIA GPUs with sufficient memory for the target models
10
+ - Python 3 with the requests library installed
11
+
12
+ ## How It Works
13
+
14
+ The skill lives at .cursor/skills/test-model/SKILL.md and is **automatically activated** when you ask the agent to test, benchmark, or validate models. It walks the agent through five phases:
15
+
16
+ | Phase | What happens |
17
+ |-------|-------------|
18
+ | **0 - Gather models** | Collects model list from a local folder scan or user-provided HuggingFace IDs. Auto-detects supported architectures and quantization formats. |
19
+ | **1 - Estimate resources** | Queries nvidia-smi for available GPUs and free memory. Estimates per-model memory requirements and assigns GPUs accordingly. |
20
+ | **2 - Build** | Compiles the project with `./build.sh --install --features cuda,nccl,flashinfer,cutlass`. |
21
+ | **3 - Create test script** | Writes test_model.py, an OpenAI-compatible API test that sends 1k+ input tokens, requests 1k+ output tokens, and measures throughput and quality. |
22
+ | **4 - Test each model** | Iteratively starts the server, waits for readiness, runs the test script (with and without reasoning/thinking), records results, kills the server, and moves to the next model. |
23
+ | **5 - Summarize** | Produces a markdown table with per-model results: format, GPUs, throughput, and quality verdict. |
24
+
25
+ ## Quick Start
26
+
27
+ Open the project in Cursor and ask the agent:
28
+
29
+ ```
30
+ Test all models in /data/
31
+ ```
32
+
33
+ The agent will scan the directory, identify compatible models, and test each one automatically.
34
+
35
+ You can also specify individual models:
36
+
37
+ ```
38
+ Test models AxionML/Qwen3.5-2B-NVFP4, Qwen/Qwen3-4B
39
+ ```
40
+
41
+ Or mix local paths and HuggingFace IDs:
42
+
43
+ ```
44
+ Test /data/Qwen3.5-27B-FP8 and AxionML/Qwen3.5-9B-NVFP4
45
+ ```
46
+
47
+ ## What Gets Tested
48
+
49
+ For each model, the skill tests:
50
+
51
+ | Aspect | Details |
52
+ |--------|---------|
53
+ | **Loading** | Server starts and model is accessible via /v1/models |
54
+ | **Inference (no thinking)** | 1k+ input tokens, 2k output tokens, thinking=false |
55
+ | **Inference (with thinking)** | Same prompt with thinking=true / reasoning enabled |
56
+ | **Output quality** | Coherence check, repetition detection (3-gram analysis) |
57
+ | **Performance** | End-to-end throughput in tokens/second |
58
+
59
+ ## Supported Model Formats
60
+
61
+ | Format | Detection method |
62
+ |--------|-----------------|
63
+ | **BF16 / FP16** (safetensors) | No quantization_config in config.json |
64
+ | **FP8** (blockwise) | quant_method: fp8 |
65
+ | **MXFP4** | quant_method: mxfp4 or format contains mxfp4 |
66
+ | **NVFP4** (modelopt) | quant_method: modelopt with quant_algo: NVFP4 |
67
+ | **NVFP4** (compressed-tensors) | format contains nvfp4 |
68
+ | **GGUF** | .gguf file present in directory |
69
+ | **GPTQ / AWQ** | quant_method: gptq or awq |
70
+
71
+ ## GPU Assignment
72
+
73
+ The agent automatically assigns GPUs based on available memory:
74
+
75
+ - **Single GPU**: Models that fit in one GPU's free memory
76
+ - **Multi-GPU**: Large models that require 2+ GPUs (uses --d 0,1 etc.)
77
+ - **Skip**: Models that exceed all available GPU memory
78
+
79
+ ## Debugging Failures
80
+
81
+ If a model fails to load or produce output, the skill guides the agent through:
82
+
83
+ 1. Checking server logs for panics or weight-loading errors
84
+ 2. Enabling RUST_BACKTRACE=1 for stack traces
85
+ 3. Temporarily using guard.step().unwrap() in engine.rs for crash debugging
86
+ 4. Identifying common issues (MLX format incompatibility, quantization detection, OOM)
87
+
88
+ ## Example Output (Decoding performance)
89
+
90
+ | # | Model | Format | GPUs | thinking=false | thinking=true | Quality |
91
+ |---|-------|--------|------|----------------|---------------|---------|
92
+ | 1 | Qwen3-4B-FP8 | FP8 | 1 | 1329 in / 2048 out, 168 tok/s | 1329 in / 2048 out, 169 tok/s | OK |
93
+ | 2 | Qwen3.5-27B | BF16 | 1 | 1342 in / 2048 out, 28 tok/s | 1342 in / 2048 out, 27 tok/s | OK |
94
+ | 3 | Qwen3.5-35B-A3B-GGUF | Q3_K_M | 1 | 1342 in / 2048 out, 95 tok/s | 1342 in / 2048 out, 98 tok/s | OK |
95
+
96
+ ## File Reference
97
+
98
+ | File | Role |
99
+ |------|------|
100
+ | .cursor/skills/test-model/SKILL.md | The skill definition (read by the AI agent) |
101
+ | test_model.py | OpenAI API test script (created by the skill) |
102
+ | build.sh | Build script for the xinfer binary |
103
+ | src/core/engine.rs | Engine loop, guard.step() location for debug |
104
+ | src/models/layers/deltanet.rs | DeltaNet layer with per-weight quantization detection |
@@ -0,0 +1,116 @@
1
+ # Tokenizer API
2
+
3
+ The Tokenizer API provides direct access to the model's tokenizer for encoding/decoding text without running inference. This is useful for:
4
+
5
+ - Pre-computing token counts for cost estimation
6
+ - Validating inputs before sending to the model
7
+ - Debugging tokenization issues
8
+ - Building custom tooling around the tokenizer
9
+
10
+ ## Endpoints
11
+
12
+ ### POST /tokenize
13
+
14
+ Convert text or chat messages to token IDs.
15
+
16
+ **Request (plain text):**
17
+ ```json
18
+ {
19
+ "prompt": "Hello, world!"
20
+ }
21
+ ```
22
+
23
+ **Request (chat messages - applies chat template):**
24
+ ```json
25
+ {
26
+ "messages": [
27
+ {"role": "system", "content": "You are helpful."},
28
+ {"role": "user", "content": "Hello!"}
29
+ ]
30
+ }
31
+ ```
32
+
33
+ **Optional parameters:**
34
+ - `model`: Model name (optional, uses loaded model)
35
+ - `add_special_tokens`: Whether to add special tokens (default: `true`)
36
+
37
+ **Response:**
38
+ ```json
39
+ {
40
+ "tokens": [1, 2, 3, 4],
41
+ "count": 4,
42
+ "max_model_len": 4096
43
+ }
44
+ ```
45
+
46
+ ### POST /detokenize
47
+
48
+ Convert token IDs back to text.
49
+
50
+ **Request:**
51
+ ```json
52
+ {
53
+ "tokens": [1, 2, 3, 4]
54
+ }
55
+ ```
56
+
57
+ **Optional parameters:**
58
+ - `model`: Model name (optional, uses loaded model)
59
+ - `skip_special_tokens`: Whether to skip special tokens in output (default: `true`)
60
+
61
+ **Response:**
62
+ ```json
63
+ {
64
+ "prompt": "Hello, world!"
65
+ }
66
+ ```
67
+
68
+ ## Examples
69
+
70
+ ### Python
71
+
72
+ ```python
73
+ import requests
74
+
75
+ # Tokenize text
76
+ response = requests.post("http://localhost:8000/tokenize", json={
77
+ "prompt": "Hello, world!"
78
+ })
79
+ print(response.json())
80
+ # {"tokens": [9906, 11, 1917, 0], "count": 4, "max_model_len": 4096}
81
+
82
+ # Detokenize
83
+ response = requests.post("http://localhost:8000/detokenize", json={
84
+ "tokens": [9906, 11, 1917, 0]
85
+ })
86
+ print(response.json())
87
+ # {"prompt": "Hello, world!"}
88
+ ```
89
+
90
+ ### cURL
91
+
92
+ ```bash
93
+ # Tokenize
94
+ curl -X POST http://localhost:8000/tokenize \
95
+ -H "Content-Type: application/json" \
96
+ -d '{"prompt": "Hello, world!"}'
97
+
98
+ # Detokenize
99
+ curl -X POST http://localhost:8000/detokenize \
100
+ -H "Content-Type: application/json" \
101
+ -d '{"tokens": [9906, 11, 1917, 0]}'
102
+ ```
103
+
104
+ ## Demo Scripts
105
+
106
+ - **Python**: `example/tokenize.py` - Interactive demo with multiple examples
107
+ - **Rust**: `example/rust-demo-tokenize/` - Rust client demo
108
+
109
+ Run the Python demo:
110
+ ```bash
111
+ # Start the server first
112
+ cargo run --release -- --m Qwen/Qwen2.5-0.5B-Instruct --server
113
+
114
+ # In another terminal
115
+ python example/tokenize.py
116
+ ```
@@ -0,0 +1,74 @@
1
+ ## Tool Call Parsing
2
+
3
+ This project uses the model-specific parsers for parsing tool calls for both
4
+ streaming and non-streaming responses. The goal is to keep parsing logic
5
+ consistent across models while remaining robust to partial output and format
6
+ differences.
7
+
8
+ ### Parser selection
9
+
10
+ Parser selection follows this order:
11
+
12
+ 1. `--enforce-parser` (if provided and valid).
13
+ 2. Model-based heuristics (model type + model id).
14
+ 3. Fallback to `passthrough`.
15
+
16
+ If you pass an invalid name to `--enforce-parser`, the server returns an error
17
+ and includes the list of valid parser names.
18
+
19
+ Available parser names:
20
+
21
+ - passthrough
22
+ - json
23
+ - mistral
24
+ - qwen
25
+ - qwen_coder
26
+ - pythonic
27
+ - llama
28
+ - deepseek
29
+ - glm45_moe
30
+ - glm47_moe
31
+ - step3
32
+ - kimik2
33
+ - minimax_m2
34
+
35
+ ### Streaming parsing
36
+
37
+ Streaming requests use incremental parsing logic. Each incoming token is appended to an internal buffer and fed into the parser. The parser emits streaming tool call fragments, which are accumulated into full tool calls.
38
+
39
+ When an end marker is detected (token id or `</tool_call>` tag), the stream parser:
40
+
41
+ 1. Flushes any unstreamed arguments from the external parser.
42
+ 2. Builds tool calls from the accumulated fragments.
43
+ 3. If no tool calls were produced, falls back to `parse_complete_with_fallback`
44
+ on the buffered content.
45
+
46
+ If parsing still fails, the buffered content is emitted as normal text so the
47
+ client does not lose output.
48
+
49
+ ### Non-streaming parsing
50
+
51
+ Non-streaming requests reuse the same stream parser and call
52
+ `parse_complete_with_fallback`. This keeps parser selection and fallback logic
53
+ identical between streaming and non-streaming paths.
54
+
55
+ ### Enforcing a parser
56
+
57
+ CLI (Rust server):
58
+
59
+ ```
60
+ --enforce-parser qwen_coder
61
+ ```
62
+
63
+ Python server example (`server.py` or `xinfer.server`):
64
+
65
+ ```
66
+ --enforce-parser qwen_coder
67
+ ```
68
+
69
+ ### Environment Variables
70
+
71
+ - `XINFER_STRICT_TOOL_CALL`:
72
+ - `1` or `true`: Strict validation. Dropping invalid tool calls (calls that do not match the schema) effectively preventing them from being sent to the client. The server logs a warning for dropped calls.
73
+ - `0` or `false` (default): Lenient validation. Invalid tool calls are kept and sent to the client, but a warning is logged by the server. This allows models to output "hallucinated" or malformed calls if desired.
74
+