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.
- package/LICENSE.txt +21 -0
- package/README.md +595 -0
- package/bin/xinfer.js +66 -0
- package/docs/add_model.md +88 -0
- package/docs/claude_code.md +95 -0
- package/docs/docker.md +73 -0
- package/docs/embeddings.md +33 -0
- package/docs/get_started.md +93 -0
- package/docs/goose.md +83 -0
- package/docs/guided_decoding.md +474 -0
- package/docs/kilocode.md +69 -0
- package/docs/mcp_tool_calling.md +155 -0
- package/docs/multimodal.md +37 -0
- package/docs/opencode.md +83 -0
- package/docs/performance.md +65 -0
- package/docs/prefix-cache.md +79 -0
- package/docs/rust_crate.md +119 -0
- package/docs/test_model.md +104 -0
- package/docs/tokenizer_api.md +116 -0
- package/docs/tool_parsing.md +74 -0
- package/docs/xbot.md +110 -0
- package/package.json +49 -0
- package/scripts/install.js +211 -0
package/docs/xbot.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# xbot + xInfer
|
|
2
|
+
|
|
3
|
+
This guide connects `xbot` directly to `xInfer` through the built-in OpenAI-compatible `/v1/chat/completions` endpoint.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
xbot -> xInfer (OpenAI-compatible)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 1) Start xInfer (at port 8000)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
./run --release --features cuda,nccl,flashinfer,cutlass \
|
|
13
|
+
--m Qwen/Qwen3.6-27B-FP8
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
or
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
# Python
|
|
20
|
+
python3 -m xinfer.server --m Qwen/Qwen3.6-27B-FP8
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2) Discover the served model name
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl http://localhost:8000/v1/models
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use the returned `id` in the xbot config.
|
|
30
|
+
|
|
31
|
+
## 3) Configure xbot
|
|
32
|
+
|
|
33
|
+
Install xbot:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @trusted-ai/xbot
|
|
37
|
+
```
|
|
38
|
+
Or:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cargo install xbot
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run xbot provider config:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
xbot onboard
|
|
48
|
+
xbot config --provider
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Refer to the following selections:
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
> Select provider to configure: custom
|
|
55
|
+
> Enter a unique name for this custom provider: xinfer
|
|
56
|
+
> Enter API Base URL (e.g. https://api.yourprovider.com/v1): http://localhost:8000/v1/
|
|
57
|
+
> Enter API Key? (Optional for local/custom) No
|
|
58
|
+
|
|
59
|
+
Fetching available models...
|
|
60
|
+
> Select default model: Qwen3.6-27B-FP8
|
|
61
|
+
Using contextWindowTokens from model metadata: 262144
|
|
62
|
+
|
|
63
|
+
> Configure subagent provider/model now? No
|
|
64
|
+
|
|
65
|
+
Configuration saved successfully!
|
|
66
|
+
Config file: /root/.xbot/config.json
|
|
67
|
+
|
|
68
|
+
Final Provider Config:
|
|
69
|
+
{
|
|
70
|
+
"api_key": "",
|
|
71
|
+
"api_base": "http://localhost:8000/v1/",
|
|
72
|
+
"extra_headers": {},
|
|
73
|
+
"reasoning_effort": null
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 4) Run xbot
|
|
78
|
+
|
|
79
|
+
One-shot task
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cd YOUR PROJECT
|
|
83
|
+
# scan project and init agent file
|
|
84
|
+
xbot chat /init
|
|
85
|
+
# real task
|
|
86
|
+
xbot chat "find bugs in this project."
|
|
87
|
+
```
|
|
88
|
+
**Interactive terminal** (rich TUI)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd YOUR PROJECT
|
|
92
|
+
# you may add .xbot workspace folder to .gitignore
|
|
93
|
+
xbot repl
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Notes
|
|
97
|
+
|
|
98
|
+
- Tool calls follow the normal OpenAI request/response loop.
|
|
99
|
+
- For Qwen coder models, `--enforce-parser qwen_coder` is usually the most reliable parser setting.
|
|
100
|
+
- If xbot reports a model mismatch, compare your configured model against `GET /v1/models`.
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
Chat logger:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Log into files (in folder ./log)
|
|
108
|
+
export XINFER_CHAT_LOGGER=1
|
|
109
|
+
```
|
|
110
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xinfer-ai",
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "xInfer — a high-performance LLM inference engine in Rust with CUDA/Metal acceleration",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/guoqingbao/xinfer#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/guoqingbao/xinfer.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/guoqingbao/xinfer/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"xinfer": "bin/xinfer.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"prepack": "node scripts/prepare-package.js",
|
|
19
|
+
"postpack": "node scripts/clean-package.js",
|
|
20
|
+
"postinstall": "node scripts/install.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/xinfer.js",
|
|
24
|
+
"scripts/install.js",
|
|
25
|
+
"docs",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE.txt"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"llm",
|
|
31
|
+
"inference",
|
|
32
|
+
"cuda",
|
|
33
|
+
"metal",
|
|
34
|
+
"gpu",
|
|
35
|
+
"xinfer",
|
|
36
|
+
"rust"
|
|
37
|
+
],
|
|
38
|
+
"os": [
|
|
39
|
+
"darwin",
|
|
40
|
+
"linux"
|
|
41
|
+
],
|
|
42
|
+
"cpu": [
|
|
43
|
+
"arm64",
|
|
44
|
+
"x64"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { createHash } = require("crypto");
|
|
9
|
+
const { spawnSync } = require("child_process");
|
|
10
|
+
|
|
11
|
+
const pkg = require("../package.json");
|
|
12
|
+
|
|
13
|
+
function targetTriple() {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
const arch = process.arch;
|
|
16
|
+
if (
|
|
17
|
+
(platform !== "linux" && platform !== "darwin") ||
|
|
18
|
+
(arch !== "x64" && arch !== "arm64")
|
|
19
|
+
) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Unsupported platform: ${platform}-${arch}. Supported: linux/darwin x64/arm64.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return `${platform}-${arch}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detect CUDA compute capability via nvidia-smi.
|
|
29
|
+
* Returns the highest SM version across all GPUs (e.g. "90", "89", "80").
|
|
30
|
+
* Returns null if no NVIDIA GPU detected (e.g. macOS Metal).
|
|
31
|
+
*/
|
|
32
|
+
function detectCudaComputeCap() {
|
|
33
|
+
// Allow explicit override
|
|
34
|
+
if (process.env.XINFER_CUDA_COMPUTE_CAP) {
|
|
35
|
+
return process.env.XINFER_CUDA_COMPUTE_CAP;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = spawnSync(
|
|
40
|
+
"nvidia-smi",
|
|
41
|
+
[
|
|
42
|
+
"--query-gpu=compute_cap",
|
|
43
|
+
"--format=csv,noheader,nounits",
|
|
44
|
+
],
|
|
45
|
+
{ encoding: "utf8", timeout: 10000 }
|
|
46
|
+
);
|
|
47
|
+
if (result.status !== 0 || !result.stdout) return null;
|
|
48
|
+
|
|
49
|
+
let maxCap = 0;
|
|
50
|
+
for (const line of result.stdout.trim().split("\n")) {
|
|
51
|
+
const cap = line.trim();
|
|
52
|
+
if (!cap) continue;
|
|
53
|
+
// Parse "8.9" -> 89, "9.0" -> 90, "10.0" -> 100
|
|
54
|
+
const parts = cap.split(".");
|
|
55
|
+
const major = parseInt(parts[0], 10);
|
|
56
|
+
const minor = parseInt(parts[1] || "0", 10);
|
|
57
|
+
const sm = major * 10 + minor;
|
|
58
|
+
if (sm > maxCap) maxCap = sm;
|
|
59
|
+
}
|
|
60
|
+
return maxCap > 0 ? String(maxCap) : null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Map detected SM version to the build variant name.
|
|
68
|
+
* Build variants match CI matrix: sm70, sm75, sm80, sm89, sm90, sm120
|
|
69
|
+
*/
|
|
70
|
+
function smToVariant(sm) {
|
|
71
|
+
const n = parseInt(sm, 10);
|
|
72
|
+
if (n >= 120) return "sm120";
|
|
73
|
+
if (n >= 90) return "sm90";
|
|
74
|
+
if (n >= 89) return "sm89";
|
|
75
|
+
if (n >= 80) return "sm80";
|
|
76
|
+
if (n >= 75) return "sm75";
|
|
77
|
+
if (n >= 70) return "sm70";
|
|
78
|
+
return "sm80";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function download(url, destination, redirects = 0) {
|
|
82
|
+
if (redirects > 5) {
|
|
83
|
+
return Promise.reject(
|
|
84
|
+
new Error(`Too many redirects while downloading ${url}`)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const proto = url.startsWith("https") ? https : require("http");
|
|
89
|
+
const request = proto.get(url, (response) => {
|
|
90
|
+
if (
|
|
91
|
+
response.statusCode >= 300 &&
|
|
92
|
+
response.statusCode < 400 &&
|
|
93
|
+
response.headers.location
|
|
94
|
+
) {
|
|
95
|
+
response.resume();
|
|
96
|
+
const next = new URL(response.headers.location, url).toString();
|
|
97
|
+
download(next, destination, redirects + 1).then(resolve, reject);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (response.statusCode !== 200) {
|
|
101
|
+
response.resume();
|
|
102
|
+
reject(
|
|
103
|
+
new Error(`Download failed (${response.statusCode}) for ${url}`)
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const file = fs.createWriteStream(destination);
|
|
108
|
+
response.pipe(file);
|
|
109
|
+
file.on("finish", () => file.close(resolve));
|
|
110
|
+
file.on("error", reject);
|
|
111
|
+
});
|
|
112
|
+
request.on("error", reject);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function sha256(file) {
|
|
117
|
+
const hash = createHash("sha256");
|
|
118
|
+
hash.update(fs.readFileSync(file));
|
|
119
|
+
return hash.digest("hex");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function expectedSha(manifest, artifactName) {
|
|
123
|
+
for (const line of manifest.split(/\r?\n/)) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed) continue;
|
|
126
|
+
const parts = trimmed.split(/\s+/);
|
|
127
|
+
const checksum = parts[0];
|
|
128
|
+
const name = parts[parts.length - 1].replace(/^\*/, "");
|
|
129
|
+
if (name === artifactName) return checksum;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function main() {
|
|
135
|
+
const target = targetTriple();
|
|
136
|
+
const version = process.env.XINFER_INSTALL_VERSION || pkg.version;
|
|
137
|
+
const tag = process.env.XINFER_INSTALL_TAG || `v${version}`;
|
|
138
|
+
const base =
|
|
139
|
+
process.env.XINFER_INSTALL_BASE_URL ||
|
|
140
|
+
`https://github.com/guoqingbao/xinfer/releases/download/${tag}`;
|
|
141
|
+
|
|
142
|
+
let variant = "";
|
|
143
|
+
if (process.platform === "linux") {
|
|
144
|
+
const sm = detectCudaComputeCap();
|
|
145
|
+
if (sm) {
|
|
146
|
+
variant = `-${smToVariant(sm)}`;
|
|
147
|
+
console.log(
|
|
148
|
+
`Detected CUDA compute capability: sm_${sm} -> variant${variant}`
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
console.log(
|
|
152
|
+
"No NVIDIA GPU detected; downloading default (sm80) build."
|
|
153
|
+
);
|
|
154
|
+
variant = "-sm80";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// macOS uses metal variant
|
|
158
|
+
if (process.platform === "darwin") {
|
|
159
|
+
variant = "-metal";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const artifactName = `xinfer-${version}-${target}${variant}.tar.gz`;
|
|
163
|
+
const artifactUrl = `${base}/${artifactName}`;
|
|
164
|
+
const sumsUrl = `${base}/SHA256SUMS`;
|
|
165
|
+
const root = path.resolve(__dirname, "..");
|
|
166
|
+
const installDir = path.join(root, "vendor", `${target}${variant}`);
|
|
167
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "xinfer-install-"));
|
|
168
|
+
const archivePath = path.join(tmpDir, artifactName);
|
|
169
|
+
const sumsPath = path.join(tmpDir, "SHA256SUMS");
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
console.log(`Downloading ${artifactUrl}...`);
|
|
173
|
+
await download(sumsUrl, sumsPath);
|
|
174
|
+
await download(artifactUrl, archivePath);
|
|
175
|
+
const expected = expectedSha(
|
|
176
|
+
fs.readFileSync(sumsPath, "utf8"),
|
|
177
|
+
artifactName
|
|
178
|
+
);
|
|
179
|
+
if (!expected) {
|
|
180
|
+
throw new Error(`No checksum entry found for ${artifactName}`);
|
|
181
|
+
}
|
|
182
|
+
const actual = sha256(archivePath);
|
|
183
|
+
if (actual !== expected) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fs.rmSync(installDir, { recursive: true, force: true });
|
|
190
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
191
|
+
const tar = spawnSync(
|
|
192
|
+
"tar",
|
|
193
|
+
["-xzf", archivePath, "-C", installDir],
|
|
194
|
+
{ stdio: "inherit" }
|
|
195
|
+
);
|
|
196
|
+
if (tar.error) throw tar.error;
|
|
197
|
+
if (tar.status !== 0) {
|
|
198
|
+
throw new Error(`tar exited with status ${tar.status}`);
|
|
199
|
+
}
|
|
200
|
+
const binary = path.join(installDir, "xinfer");
|
|
201
|
+
fs.chmodSync(binary, 0o755);
|
|
202
|
+
console.log(`xinfer installed to ${installDir}`);
|
|
203
|
+
} finally {
|
|
204
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
main().catch((err) => {
|
|
209
|
+
console.error(`xinfer install failed: ${err.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|