Any-to-Any
Transformers
Safetensors
GGUF
gemma4
multimodal
text-generation
image-text-to-text
audio-text-to-text
video-text-to-text
long-context
lora
bf16
llama-cpp
zh
en
Instructions to use chirs345678/Anrn-12B-R1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use chirs345678/Anrn-12B-R1 with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("chirs345678/Anrn-12B-R1", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add files using upload-large-folder tool
Browse files- .gitattributes +11 -30
- .gitignore +7 -0
- HF_UPLOAD_MANIFEST.json +58 -0
- README.md +152 -0
- SHA256SUMS.txt +106 -0
- Start-Llama-Service.bat +14 -0
- Stop-Llama-Service.bat +5 -0
- adapter/adapter_config.json +46 -0
- adapter/chat_template.jinja +390 -0
- adapter/tokenizer_config.json +249 -0
- audio-test-result.json +24 -0
- llama-bootstrap-verification.json +95 -0
- llama-bootstrap.ps1 +224 -0
- llama-identity-test.json +58 -0
- llama-model/chat_template.prompt30k.jinja +0 -0
- llama-model/model-verification.json +11 -0
- llama-model/ui-config.json +13 -0
- llama-runtime-state.json +20 -0
- llama-runtime/ggml.dll +0 -0
- llama-runtime/llama-batched-bench-impl.dll +0 -0
- llama-runtime/llama-batched-bench.exe +0 -0
- llama-runtime/llama-bench.exe +0 -0
- llama-runtime/llama-cli.exe +0 -0
- llama-runtime/llama-completion.exe +0 -0
- llama-runtime/llama-fit-params-impl.dll +0 -0
- llama-runtime/llama-fit-params.exe +0 -0
- llama-runtime/llama-gemma3-cli.exe +0 -0
- llama-runtime/llama-gguf-split.exe +0 -0
- llama-runtime/llama-llava-cli.exe +0 -0
- llama-runtime/llama-minicpmv-cli.exe +0 -0
- llama-runtime/llama-mtmd-cli.exe +0 -0
- llama-runtime/llama-mtmd-debug.exe +0 -0
- package.json +26 -0
- prompt30k-load-test.json +90 -0
- prompt30k-postpatch-text-test.json +28 -0
- prompt30k-verification.json +184 -0
- requirements.txt +10 -0
- rollback-llama-bootstrap.ps1 +35 -0
- rollback-prompt30k.ps1 +8 -0
- rollback/prompt30k-baseline/smoke-test.ps1 +109 -0
- rollback/prompt30k-baseline/start.ps1 +99 -0
- serve.py +376 -0
- smoke-test-result.json +21 -0
- smoke-test.ps1 +141 -0
- start.ps1 +108 -0
- system_prompt.md +0 -0
- test-video.mp4 +0 -0
- test-whoami.png +0 -0
- verification.json +63 -0
- video-test-result.json +24 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,16 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.
|
| 25 |
-
*.
|
| 26 |
-
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.gguf filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.dll filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.exe filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 11 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.cache/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.stderr.log
|
| 5 |
+
*.stdout.log
|
| 6 |
+
*-service-state.json
|
| 7 |
+
|
HF_UPLOAD_MANIFEST.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo_id": "chirs345678/Anrn-12B-R1",
|
| 3 |
+
"display_name": "Anrn 12B R1",
|
| 4 |
+
"source_variant": "full-bf16-original",
|
| 5 |
+
"staging_root": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\hf-staging\\Anrn-12B-R1",
|
| 6 |
+
"files": 108,
|
| 7 |
+
"payload_bytes": 33416349452,
|
| 8 |
+
"benchmark_image": "assets/anrn-12b-r1-benchmarks.jpg",
|
| 9 |
+
"benchmark_results": {
|
| 10 |
+
"MMLU-Pro": 74.2,
|
| 11 |
+
"GPQA Diamond": 77.8,
|
| 12 |
+
"AIME 2026 (no tools)": 69.6,
|
| 13 |
+
"LiveCodeBench v6": 68.3,
|
| 14 |
+
"Codeforces Elo": 1521,
|
| 15 |
+
"BigBench Extra Hard": 48.2,
|
| 16 |
+
"Tau2 Average": 63.2,
|
| 17 |
+
"MMMU Pro": 62.1,
|
| 18 |
+
"MRCR v2 (128K)": 42.6
|
| 19 |
+
},
|
| 20 |
+
"large_files": [
|
| 21 |
+
{
|
| 22 |
+
"path": "adapter/adapter_model.safetensors",
|
| 23 |
+
"bytes": 131224632,
|
| 24 |
+
"sha256": "6FDA7AD849C2208C7A8D549B0F9588B5B2BF789B0FAF2E44427B8D73171C44B6"
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"path": "llama-model/anru-final-lora-Q8_0.gguf",
|
| 28 |
+
"bytes": 51682496,
|
| 29 |
+
"sha256": "3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037"
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"path": "llama-model/anru-gemma4-12b-base-Q5_K_M.gguf",
|
| 33 |
+
"bytes": 8547249568,
|
| 34 |
+
"sha256": "057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752"
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"path": "llama-runtime/cublas64_13.dll",
|
| 38 |
+
"bytes": 51870320,
|
| 39 |
+
"sha256": "F1D500D0CD892F5B8C6B6CDBFFD82D0C55D5F5427215668E7CEB55AEECCC1B63"
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"path": "llama-runtime/cublasLt64_13.dll",
|
| 43 |
+
"bytes": 460301424,
|
| 44 |
+
"sha256": "B592CD016D7673E9CB97716A22B27C4010EE635377A3BA28F37070A9BDB76A68"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"path": "llama-runtime/ggml-cuda.dll",
|
| 48 |
+
"bytes": 141608448,
|
| 49 |
+
"sha256": "A1F0ED3DC2D80FA7B4C33A3156C99F79DA782152A05AF87F4D4687A459AB6C41"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"path": "model/model.safetensors",
|
| 53 |
+
"bytes": 23919549408,
|
| 54 |
+
"sha256": "FE054AE05FF7F44318FD8AE90D58992531455C7ED31356704088F0F2D8C8009A"
|
| 55 |
+
}
|
| 56 |
+
],
|
| 57 |
+
"hashing_seconds": 30.859
|
| 58 |
+
}
|
README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
license_link: https://ai.google.dev/gemma/docs/gemma_4_license
|
| 4 |
+
library_name: transformers
|
| 5 |
+
pipeline_tag: any-to-any
|
| 6 |
+
base_model: google/gemma-4-12B-it
|
| 7 |
+
tags:
|
| 8 |
+
- gemma4
|
| 9 |
+
- multimodal
|
| 10 |
+
- text-generation
|
| 11 |
+
- image-text-to-text
|
| 12 |
+
- audio-text-to-text
|
| 13 |
+
- video-text-to-text
|
| 14 |
+
- long-context
|
| 15 |
+
- lora
|
| 16 |
+
- bf16
|
| 17 |
+
- llama-cpp
|
| 18 |
+
- zh
|
| 19 |
+
- en
|
| 20 |
+
model_name: Anrn-12B-R1
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
# Anrn 12B R1
|
| 24 |
+
|
| 25 |
+
**Anrn 12B R1** is a 12B dense, natively multimodal Gemma 4 derivative distributed as a byte-identical BF16 base plus a project LoRA adapter applied at a tested scale of `0.45`. The release supports text, image, audio, and video inputs through the native Transformers service and includes a ready-to-run llama.cpp text deployment.
|
| 26 |
+
|
| 27 |
+
> **Release layout:** this repository intentionally keeps the original BF16 base and the trained adapter separate rather than destructively merging them. The included launchers compose both parts at runtime.
|
| 28 |
+
|
| 29 |
+
## Benchmark results
|
| 30 |
+
|
| 31 |
+

|
| 32 |
+
|
| 33 |
+
| Benchmark | Anrn 12B R1 |
|
| 34 |
+
|---|---:|
|
| 35 |
+
| MMLU-Pro | **74.2** |
|
| 36 |
+
| GPQA Diamond | **77.8** |
|
| 37 |
+
| AIME 2026 (no tools) | **69.6** |
|
| 38 |
+
| LiveCodeBench v6 | **68.3** |
|
| 39 |
+
| Codeforces Elo | **1521** |
|
| 40 |
+
| BigBench Extra Hard | **48.2** |
|
| 41 |
+
| Tau2 Average | **63.2** |
|
| 42 |
+
| MMMU Pro | **62.1** |
|
| 43 |
+
| MRCR v2 (128K) | **42.6** |
|
| 44 |
+
|
| 45 |
+
The Anrn values above come from the project's August 2026 evaluation run. The comparison values shown in the chart are reproduced from the published benchmark results used by that report; values marked with `~` are approximate.
|
| 46 |
+
|
| 47 |
+
## Model details
|
| 48 |
+
|
| 49 |
+
| Field | Value |
|
| 50 |
+
|---|---|
|
| 51 |
+
| Architecture | Gemma 4 Unified, dense decoder-only multimodal model |
|
| 52 |
+
| Parameters | 11.95B |
|
| 53 |
+
| Base model | `google/gemma-4-12B-it` |
|
| 54 |
+
| Native precision | BF16 |
|
| 55 |
+
| Adaptation | LoRA, rank 8, alpha 16 |
|
| 56 |
+
| Tested adapter scale | `0.45` |
|
| 57 |
+
| Native modalities | Text, image, audio, video input; text output |
|
| 58 |
+
| Tested service context | 131,072 tokens |
|
| 59 |
+
| Model configuration maximum | 262,144 positions |
|
| 60 |
+
| Languages | Multilingual; optimized project behavior in Chinese and English |
|
| 61 |
+
| Default prompt | Project constitution embedded in `system_prompt.md` |
|
| 62 |
+
|
| 63 |
+
## Repository structure
|
| 64 |
+
|
| 65 |
+
```text
|
| 66 |
+
model/ Original BF16 Gemma 4 12B weights and processor files
|
| 67 |
+
adapter/ Anrn R1 PEFT/LoRA adapter
|
| 68 |
+
llama-model/ Q5_K_M GGUF base, Q8_0 GGUF LoRA, and embedded chat template
|
| 69 |
+
llama-runtime/ Verified Windows CUDA llama.cpp runtime
|
| 70 |
+
assets/ Benchmark figure
|
| 71 |
+
start.ps1 Native multimodal OpenAI-compatible service
|
| 72 |
+
serve.py Native service implementation
|
| 73 |
+
Start-Llama-Service.bat
|
| 74 |
+
One-click llama.cpp text Web UI
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## Quick start: native multimodal service
|
| 78 |
+
|
| 79 |
+
Requirements: Windows, Python, a recent CUDA-capable NVIDIA GPU, and PowerShell.
|
| 80 |
+
|
| 81 |
+
```powershell
|
| 82 |
+
python -m pip install -r requirements.txt
|
| 83 |
+
.\start.ps1 -Port 8091
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
The launcher loads `model/`, mounts `adapter/` at scale `0.45`, injects `system_prompt.md`, uses PyTorch SDPA for long-context inference, and automatically offloads language layers to system memory when required. The tested configuration used approximately 14 GB of GPU memory.
|
| 87 |
+
|
| 88 |
+
OpenAI-compatible endpoint:
|
| 89 |
+
|
| 90 |
+
```text
|
| 91 |
+
POST http://127.0.0.1:8091/v1/chat/completions
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Example health check:
|
| 95 |
+
|
| 96 |
+
```powershell
|
| 97 |
+
Invoke-RestMethod http://127.0.0.1:8091/health
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
## Quick start: llama.cpp Web UI
|
| 101 |
+
|
| 102 |
+
On Windows, double-click:
|
| 103 |
+
|
| 104 |
+
```text
|
| 105 |
+
Start-Llama-Service.bat
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
The bootstrap checks the bundled llama.cpp runtime and, if it is missing, downloads a matching official Windows CUDA/CPU build. It then starts the text Web UI with:
|
| 109 |
+
|
| 110 |
+
- 131,072-token context
|
| 111 |
+
- LoRA scale `0.45`
|
| 112 |
+
- Flash Attention
|
| 113 |
+
- Q8_0 K/V cache
|
| 114 |
+
- the project system prompt embedded in the Jinja chat template
|
| 115 |
+
|
| 116 |
+
Web UI: `http://127.0.0.1:8091`
|
| 117 |
+
|
| 118 |
+
Use `Stop-Llama-Service.bat` to stop it. The llama.cpp route is text-only; use `start.ps1` for native image, audio, and video inputs.
|
| 119 |
+
|
| 120 |
+
## Prompt behavior
|
| 121 |
+
|
| 122 |
+
`system_prompt.md` contains the release's default project constitution. The native server injects it before caller-provided system content. A request may set:
|
| 123 |
+
|
| 124 |
+
```json
|
| 125 |
+
{"use_default_system_prompt": false}
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
to disable the default prompt for that request. The llama.cpp prompt is embedded in `llama-model/chat_template.prompt30k.jinja`.
|
| 129 |
+
|
| 130 |
+
## Verification
|
| 131 |
+
|
| 132 |
+
The release contains machine-readable verification artifacts covering:
|
| 133 |
+
|
| 134 |
+
- native text, image, audio, and video loading
|
| 135 |
+
- prompt hash and injection behavior
|
| 136 |
+
- llama.cpp health, 128K context, and live LoRA scale
|
| 137 |
+
- identity smoke tests
|
| 138 |
+
- package checksums and rollback scripts
|
| 139 |
+
|
| 140 |
+
Primary records: `verification.json`, `llama-bootstrap-verification.json`, `prompt30k-verification.json`, and `SHA256SUMS.txt`.
|
| 141 |
+
|
| 142 |
+
## Limitations
|
| 143 |
+
|
| 144 |
+
- Benchmark scores are sensitive to prompt templates, decoding parameters, judge versions, and harness revisions.
|
| 145 |
+
- The BF16 base is large; CPU offload improves accessibility but reduces generation speed.
|
| 146 |
+
- The included llama.cpp path is an auxiliary quantized text deployment and does not expose the native multimodal input path.
|
| 147 |
+
- Users should evaluate the model for their target language, domain, and deployment policy before production use.
|
| 148 |
+
|
| 149 |
+
## License
|
| 150 |
+
|
| 151 |
+
Apache 2.0. See the [Gemma 4 license information](https://ai.google.dev/gemma/docs/gemma_4_license) and the upstream model terms.
|
| 152 |
+
|
SHA256SUMS.txt
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
F8F1F88E4AF2913454B2DBDF093232DA3514242B0809821990636FE05CC5F220 .gitattributes
|
| 2 |
+
A82EDDABD088863ED306346A2D4A608D0CCFD2F50B523FAA3706940BBA022413 .gitignore
|
| 3 |
+
28D41E8ACA43CD9EED899303DE9C62DA7E4FCFC5FA49A246233E025B005D8538 README.md
|
| 4 |
+
CCE093E1A90DFEB54161D51988157AFD109085CADEC97EA55A71562A91067B5B Start-Llama-Service.bat
|
| 5 |
+
45406CD7235E9626B3A31297A61D3084629673DDF5D60766785E0EA9A1DF82FB Stop-Llama-Service.bat
|
| 6 |
+
BA2B02E2D8D2CD9BCD216CE4D742973E9B098B90A141F9AE148CB6030EBC7697 adapter/adapter_config.json
|
| 7 |
+
6FDA7AD849C2208C7A8D549B0F9588B5B2BF789B0FAF2E44427B8D73171C44B6 adapter/adapter_model.safetensors
|
| 8 |
+
8375280D2273AA1166CA393C7FC50EE21A932FD04BC439388EC2C23AE2D84615 adapter/chat_template.jinja
|
| 9 |
+
12BAC982B793C44B03D52A250A9F0D0B666813DA566B910C24A6DA0695FD11E6 adapter/tokenizer.json
|
| 10 |
+
BA1403FC298AEF0A2D81593BC81AE9F8B9D6B9BB7CE6E9DBA3D4E8528000CED5 adapter/tokenizer_config.json
|
| 11 |
+
891DAFE8B5A5BC72AFA136B6C430694BB6617D6507D30AC57A6EE974BEC548B9 assets/anrn-12b-r1-benchmarks.jpg
|
| 12 |
+
AFDEDD4F71695579D27B88B1D1D6C30C6742F20B7A920DC0240694742A2FE3B8 audio-test-result.json
|
| 13 |
+
F822CC94FC1CC08E1F137F28116DA2403FBD14689EF9FED74200848EE9C039DB llama-bootstrap-verification.json
|
| 14 |
+
AE14FA0C5F67CC255931A33DAA573670D9CE5AB38537880358ABAFA5CE39296E llama-bootstrap.ps1
|
| 15 |
+
27444160B50E33AD4033A4E0D7598574FD61C53223C025AAC7EE7D4C91C3389D llama-identity-test.json
|
| 16 |
+
3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037 llama-model/anru-final-lora-Q8_0.gguf
|
| 17 |
+
057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752 llama-model/anru-gemma4-12b-base-Q5_K_M.gguf
|
| 18 |
+
63348DECD6B0842EDF8A0AB5521009CB7A7077521F68902DFFBBF96D22E7C65D llama-model/chat_template.prompt30k.jinja
|
| 19 |
+
78B61B3764BEBEED69940CDC76D0C86EE9150C239FAF9ADE838F10B198826A17 llama-model/model-verification.json
|
| 20 |
+
A94836A913B9A345B188CED1398160E69119312ACDF84F67DCEC4086F6F82C39 llama-model/ui-config.json
|
| 21 |
+
125F7144BACB367E124298D0530644C36E337364F9E752A57A9A2E238772744A llama-runtime-state.json
|
| 22 |
+
F1D500D0CD892F5B8C6B6CDBFFD82D0C55D5F5427215668E7CEB55AEECCC1B63 llama-runtime/cublas64_13.dll
|
| 23 |
+
B592CD016D7673E9CB97716A22B27C4010EE635377A3BA28F37070A9BDB76A68 llama-runtime/cublasLt64_13.dll
|
| 24 |
+
B00CA6F53699120DA815BF3E06E2E4285FAE2F201235B883DCBB50EEC51E2A2A llama-runtime/cudart64_13.dll
|
| 25 |
+
E4625DBACBFCBEA34ED402A0DCC9225FF3DF947DEAD0DD859DD9F81A983DC365 llama-runtime/ggml-base.dll
|
| 26 |
+
D2F3A7DA3E2D1985FAEC025928F2DA5BADC4629B478C0CA8083B4418C90DA7E1 llama-runtime/ggml-cpu-alderlake.dll
|
| 27 |
+
4193F2927723669EE7B47F107090F42A675B4FD84FB77403C7CE412632BE3CCC llama-runtime/ggml-cpu-cannonlake.dll
|
| 28 |
+
7933B3891929DE369149EB03BB607F8D75F2D0E81071ADCC5FCBAB6C027A1DE9 llama-runtime/ggml-cpu-cascadelake.dll
|
| 29 |
+
96F572BFFC3DDBE7D48CBAF7C7F6DFB9D75E65DC8F43E62C55810223FD1DAE36 llama-runtime/ggml-cpu-cooperlake.dll
|
| 30 |
+
258F2AD5FB79F498876A8D3F44B366BFF4B8A7DDCFB18A61C72270B8DA8886C4 llama-runtime/ggml-cpu-haswell.dll
|
| 31 |
+
B88AC4C73D93B204FDCF82B813D52E8333252B011C871858E73AE524F78089D7 llama-runtime/ggml-cpu-icelake.dll
|
| 32 |
+
9B9018EA35E928B61445C501521BA279174B32D29CB32F7B87F11FBDE1CCD590 llama-runtime/ggml-cpu-ivybridge.dll
|
| 33 |
+
F1B6BF9D333D4ECB9F085E2AC3DF7C5BAF65B8F1DBAAAA0B75AA90DD8C8B3202 llama-runtime/ggml-cpu-piledriver.dll
|
| 34 |
+
487DF33CF344FE95424AB049FB3892D7D198C42434AEA6E1B5E28D721FFC15E7 llama-runtime/ggml-cpu-sandybridge.dll
|
| 35 |
+
A786981FE76C507D7AE79FAB512118A680394DF06C0E8BB207015B3A9441DA48 llama-runtime/ggml-cpu-sapphirerapids.dll
|
| 36 |
+
0FDD262C2DF029C8BA7D417DCE3473B0AFB795AC6D9D41C60AF0FBA7D0F32997 llama-runtime/ggml-cpu-skylakex.dll
|
| 37 |
+
E3199B17D4E6349C6C19B897FFA473B4F62B77298EA237B93FEF891B074B337E llama-runtime/ggml-cpu-sse42.dll
|
| 38 |
+
D214739C43071FC37F2703D0014CDF1DFF27F98FAF23A9707071BA9D0EA1A6E1 llama-runtime/ggml-cpu-x64.dll
|
| 39 |
+
206D0301BF5AA90CBA87B763346A5A50D0AC756CB0C4DE1271CF91833FC39E08 llama-runtime/ggml-cpu-zen4.dll
|
| 40 |
+
A1F0ED3DC2D80FA7B4C33A3156C99F79DA782152A05AF87F4D4687A459AB6C41 llama-runtime/ggml-cuda.dll
|
| 41 |
+
AB7930B188E3610264C540CFDA9DF08DF3595502F8298821191083C3D2483A33 llama-runtime/ggml-rpc-server.exe
|
| 42 |
+
6B29961A29A1B2F2071F4B71BC3936EFB54C221A5EC5F03C64B54E8CCF7B0798 llama-runtime/ggml-rpc.dll
|
| 43 |
+
82C6BE59034C1C3F3DF8F44DC97E3A5EC28207825365F0ABC4D4CCE6C9C44D14 llama-runtime/ggml.dll
|
| 44 |
+
4A20C1E5C115C29771A12324513EB109BADAC72180F79481527AD79D996FFB33 llama-runtime/libomp140.x86_64.dll
|
| 45 |
+
E18D4F974C92B09144C175EED3B8F993B40ED902A5730EEAF446EAC7996FFA2E llama-runtime/llama-batched-bench-impl.dll
|
| 46 |
+
5DAC350B0BDD3F988FF096D5E4FDC1D0A8330909E3ACDA24CE08F70AF613E081 llama-runtime/llama-batched-bench.exe
|
| 47 |
+
3951ADFAC8B9A334EBAB53054BCC1FCDE26A48F2B0BAD43B8EBA49B197D7BC0A llama-runtime/llama-bench-impl.dll
|
| 48 |
+
645329C1CE729DD8450CF54554516C2080463A8594FF567A68D0ADB3876FCAF0 llama-runtime/llama-bench.exe
|
| 49 |
+
952BDE4CB028FB824F5EA9DF5D24B41092E4EAD5406E0ED2807F215D9CC7D011 llama-runtime/llama-cli-impl.dll
|
| 50 |
+
6BA5FA31CEED5C2674B569D1F2AC35E5A4833C890CE9BCC5B3C8B0CB14476450 llama-runtime/llama-cli.exe
|
| 51 |
+
0EA7114D7578EEA9A9548A09F2B73F9219790030387B883453741220E6FE5591 llama-runtime/llama-common.dll
|
| 52 |
+
C78A9F253B2394DB9850E1F9E6B7657CB91E144E12BD06B6BD321F649457F63C llama-runtime/llama-completion-impl.dll
|
| 53 |
+
960214F5131D749BB02F09623FA0D59BC45C606AE12735C6A6677217F6674781 llama-runtime/llama-completion.exe
|
| 54 |
+
62C354420415729728A426328468AB77F210DE6BA67146330BB68DCEFDD55243 llama-runtime/llama-fit-params-impl.dll
|
| 55 |
+
737C0C64BD01D7072118D733EF77D251DAEF66665CC9266EEC7E189AFE17AC17 llama-runtime/llama-fit-params.exe
|
| 56 |
+
C34A00BB099D61D27095A704973FA987FF79E177D5B95CA88C89E19DE7669FD7 llama-runtime/llama-gemma3-cli.exe
|
| 57 |
+
1E783E50F2526BC934D743895BD5C1FDFB985591C473B338485A45EEF94FE109 llama-runtime/llama-gguf-split.exe
|
| 58 |
+
9E69B61A564353DA11F448F3B067A6627D6D8C9EC7C30D3201911EF52B7E397C llama-runtime/llama-imatrix.exe
|
| 59 |
+
C34A00BB099D61D27095A704973FA987FF79E177D5B95CA88C89E19DE7669FD7 llama-runtime/llama-llava-cli.exe
|
| 60 |
+
C34A00BB099D61D27095A704973FA987FF79E177D5B95CA88C89E19DE7669FD7 llama-runtime/llama-minicpmv-cli.exe
|
| 61 |
+
98128389B6A8A7748FDC5A86214CF0A386A8411A8A04A4D05498764CA96B13C1 llama-runtime/llama-mtmd-cli.exe
|
| 62 |
+
D8D0398AFFB4F46B387E37EE4E184B1D0D7DC462504FCDAFCA7435DD45483335 llama-runtime/llama-mtmd-debug.exe
|
| 63 |
+
B82F8A529E10F95F06186BE9482FC7796F8F76B54B3BC6B81A67D809083092FB llama-runtime/llama-perplexity-impl.dll
|
| 64 |
+
6AA1A0ABBFB8F6B3FD732B47EF78942AC9F4EBB973BD2318CBEE6741DAEEA9C9 llama-runtime/llama-perplexity.exe
|
| 65 |
+
D20F27669FD194E98AB223FB7C474BC1ED7E1F473FD48AB4893CD2C3524C6D5A llama-runtime/llama-quantize-impl.dll
|
| 66 |
+
ACD074F806311041634D6598456252F39D56512EEDE7383A1981CF7291BF1EA9 llama-runtime/llama-quantize.exe
|
| 67 |
+
C34A00BB099D61D27095A704973FA987FF79E177D5B95CA88C89E19DE7669FD7 llama-runtime/llama-qwen2vl-cli.exe
|
| 68 |
+
8E8A90B6177A4BBA6578E38CAE01FD239AB3A64B3337546FFC4D76A469A23115 llama-runtime/llama-results.exe
|
| 69 |
+
F96C6C09ED2757BFAD7A66482DE41BA397AA2FEB687650DA57E82EAA53AFEDC8 llama-runtime/llama-server-impl.dll
|
| 70 |
+
18480000A62D73EBFD15CB7BDA681ED07B7A3B3191DA46140D028F1A59717169 llama-runtime/llama-server.exe
|
| 71 |
+
A3B416C8410A9CF67E98C0EE4F9F03DF6D26B8E3F8DA691F2E4180C0EE42B12A llama-runtime/llama-template-analysis.exe
|
| 72 |
+
6421B4B28773427AA4DBAD0BD4AAE89B1B9590351A5C1C1200DAAD0C1F4BDB69 llama-runtime/llama-tokenize.exe
|
| 73 |
+
5E9357249711CA4EF2FEFA503A14649CBB9ADF5F465777E0DC78C56FA31D4D5E llama-runtime/llama-tts.exe
|
| 74 |
+
D01F59E8856EC3F067653176545724F5A0137C62FA973F9F3F11C36C7681D854 llama-runtime/llama.dll
|
| 75 |
+
822946D743AD38635A321D645C3505BE67B3F9FCF14F6BA2DB3BC8CA97171FF8 llama-runtime/llama.exe
|
| 76 |
+
F14D6147A07E0D9BAEC289BACBD9AE1A6724903168C68373CCDC8AA7C9D96294 llama-runtime/mtmd.dll
|
| 77 |
+
131E26F7F1FA69445DC4B0AB98A2251811F8C3128426BF09FCEF6AC5EE16E7E4 model/MODEL_CARD.md
|
| 78 |
+
8375280D2273AA1166CA393C7FC50EE21A932FD04BC439388EC2C23AE2D84615 model/chat_template.jinja
|
| 79 |
+
14F38C5492FFC9CBCDF808647CA0C025BB5B9B4EB737526347134D500ACE6098 model/config.json
|
| 80 |
+
02B56BD11E1CD1E363E701A85A2FD7FBAA2992EC3358C1CD7CC44EAD7208F505 model/generation_config.json
|
| 81 |
+
FE054AE05FF7F44318FD8AE90D58992531455C7ED31356704088F0F2D8C8009A model/model.safetensors
|
| 82 |
+
6B938E76555B3E9946890770E1ABCD442A4718F34041A58E8139DC8AD34545C9 model/processor_config.json
|
| 83 |
+
12BAC982B793C44B03D52A250A9F0D0B666813DA566B910C24A6DA0695FD11E6 model/tokenizer.json
|
| 84 |
+
522A38334973725DBA8F7C645195B19DDA0C284F403F43273F77837679BA2EAB model/tokenizer_config.json
|
| 85 |
+
72DFF9753D72C72135E4477B77CA7EC661C97A323BE730700676E5D9F5F28954 package.json
|
| 86 |
+
7C941889A8C783790571D4C4ACF9E6DB5979BB69D633395B2546F572639B8D6D prompt30k-load-test.json
|
| 87 |
+
80130457753569DFF1CCD4BCDFE4E5645CDCE2537AF97287A9258BD46CA0C473 prompt30k-postpatch-text-test.json
|
| 88 |
+
CA7D6A37EE0BB0B2EF961CD2040B5821A787EA0F0B2D037D80466475BD35559A prompt30k-verification.json
|
| 89 |
+
779C95081E1B2A4A7F2851898D3A4992B48FC9E5AD07B9D05628D65F7705E142 requirements.txt
|
| 90 |
+
D51C244B606102DAF6071653BE5E1EE758260A18AEAA15228B017F65385B0E0A rollback-llama-bootstrap.ps1
|
| 91 |
+
ADDFF5D3F3E3D17FB44313491586E6C72363C16266BC4C6769BF1BD40E3F0487 rollback-prompt30k.ps1
|
| 92 |
+
D1F62DD1D2455598D6288FA92390FF780B2B04C871275BCC069DBC0259EF953D rollback/llama-bootstrap-baseline/package.json
|
| 93 |
+
43CAAD7025B01B4EB867775CD9C7311DF7DEA6667CF2A6B2BA8D823206471D07 rollback/prompt30k-baseline/package.json
|
| 94 |
+
8BC05AA3A3FEFDD52F2DCCEF99FA6189B2878A3262EEFD967E12939E0B89B796 rollback/prompt30k-baseline/serve.py
|
| 95 |
+
D10688C968485A63FC20CD11E397617B3D317643FCFDC0C626F03C5D9A33DA3B rollback/prompt30k-baseline/smoke-test.ps1
|
| 96 |
+
34EAF9FB4762E451937DEA8E151C27241C94E704AB9AF3920904ABF39AA79CB6 rollback/prompt30k-baseline/start.ps1
|
| 97 |
+
C76FC8871638EF0EBD3D0C601CA8977C9B9A75C2411A40475CD168C0C1C91DD1 serve.py
|
| 98 |
+
B79D6AAB414748B8C3DF6CDFBA3DC21EEEB8B0A9CC7982271E2D159E2422EBD0 smoke-test-result.json
|
| 99 |
+
BDA656DD176571801AA43AA6752E93228AA65F58BF48F5627F9850CFC8621F09 smoke-test.ps1
|
| 100 |
+
4F53D81CA99B30BE9A7AC29C120A63BDF5AFDAF329F88089F72B230BCB78D958 start.ps1
|
| 101 |
+
5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF system_prompt.md
|
| 102 |
+
2B11736E7D7F866E532F589FA7AF4E79E7A5C7A26A54C0940850DFD91B209D88 test-audio.wav
|
| 103 |
+
D26622955EAEBC2985C2C4FBD1F58AD9B1BA1530D2C9678CC44F540DE75958A3 test-video.mp4
|
| 104 |
+
B96DB267D6E31E542C4D65AB558CE0CD5C543ED4FECE34B02198D4A654DAFA0D test-whoami.png
|
| 105 |
+
E2638097CC3A1787E2B68C44070C3D9336F367187659175CB929A0A9A54240FE verification.json
|
| 106 |
+
7A9B24BB709693AD1A59562361B59DFFBCE1183D61463B3D1C1789FF5B3D30F8 video-test-result.json
|
Start-Llama-Service.bat
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
chcp 65001 >nul
|
| 4 |
+
title Anru Llama Service Bootstrap
|
| 5 |
+
echo [Anru] Checking llama.cpp runtime, model files, and port...
|
| 6 |
+
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0llama-bootstrap.ps1" -Port 8091 -Context 131072
|
| 7 |
+
if errorlevel 1 (
|
| 8 |
+
echo.
|
| 9 |
+
echo [Anru] Startup failed. Read the message above.
|
| 10 |
+
pause
|
| 11 |
+
exit /b 1
|
| 12 |
+
)
|
| 13 |
+
echo [Anru] llama-server is ready and the Web UI has been opened.
|
| 14 |
+
exit /b 0
|
Stop-Llama-Service.bat
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
chcp 65001 >nul
|
| 4 |
+
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$c=Get-NetTCPConnection -State Listen -LocalPort 8091 -ErrorAction SilentlyContinue ^| Select-Object -First 1; if($c){$p=Get-CimInstance Win32_Process -Filter ('ProcessId='+$c.OwningProcess); if($p.Name -eq 'llama-server.exe'){Stop-Process -Id $c.OwningProcess -Force; Write-Host '[Anru] llama-server stopped.'}else{Write-Host '[Anru] port 8091 belongs to another process.'}}else{Write-Host '[Anru] no service is listening on port 8091.'}"
|
| 5 |
+
pause
|
adapter/adapter_config.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": {
|
| 6 |
+
"base_model_class": "Gemma4UnifiedForCausalLM",
|
| 7 |
+
"parent_library": "transformers.models.gemma4_unified.modeling_gemma4_unified",
|
| 8 |
+
"unsloth_fixed": true
|
| 9 |
+
},
|
| 10 |
+
"base_model_name_or_path": "N:/",
|
| 11 |
+
"bias": "none",
|
| 12 |
+
"corda_config": null,
|
| 13 |
+
"ensure_weight_tying": false,
|
| 14 |
+
"eva_config": null,
|
| 15 |
+
"exclude_modules": null,
|
| 16 |
+
"fan_in_fan_out": false,
|
| 17 |
+
"inference_mode": true,
|
| 18 |
+
"init_lora_weights": true,
|
| 19 |
+
"layer_replication": null,
|
| 20 |
+
"layers_pattern": null,
|
| 21 |
+
"layers_to_transform": null,
|
| 22 |
+
"loftq_config": {},
|
| 23 |
+
"lora_alpha": 16,
|
| 24 |
+
"lora_bias": false,
|
| 25 |
+
"lora_dropout": 0.0,
|
| 26 |
+
"lora_ga_config": null,
|
| 27 |
+
"megatron_config": null,
|
| 28 |
+
"megatron_core": "megatron.core",
|
| 29 |
+
"modules_to_save": null,
|
| 30 |
+
"monteclora_config": null,
|
| 31 |
+
"peft_type": "LORA",
|
| 32 |
+
"peft_version": "0.20.0",
|
| 33 |
+
"qalora_group_size": 16,
|
| 34 |
+
"r": 8,
|
| 35 |
+
"rank_pattern": {},
|
| 36 |
+
"revision": null,
|
| 37 |
+
"target_modules": "(?:.*?(?:language|text).*?(?:self_attn|attention|attn|mixer|mlp|feed_forward|ffn|dense|mixer).*?(?:q_proj|k_proj|v_proj|o_proj|gate_proj|up_proj|down_proj))|(?:\\bmodel\\.layers\\.[\\d]{1,}\\.(?:self_attn|attention|attn|mixer|mlp|feed_forward|ffn|dense|mixer)\\.(?:(?:q_proj|k_proj|v_proj|o_proj|gate_proj|up_proj|down_proj)))",
|
| 38 |
+
"target_parameters": null,
|
| 39 |
+
"task_type": "CAUSAL_LM",
|
| 40 |
+
"trainable_token_indices": null,
|
| 41 |
+
"use_bdlora": null,
|
| 42 |
+
"use_dora": false,
|
| 43 |
+
"use_qalora": false,
|
| 44 |
+
"use_rslora": false,
|
| 45 |
+
"velora_config": null
|
| 46 |
+
}
|
adapter/chat_template.jinja
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{#
|
| 2 |
+
Template: Google Gemma 4 Canonical Chat Template
|
| 3 |
+
Author: Google Gemma Engineering Team
|
| 4 |
+
Published: 2026-07-09
|
| 5 |
+
Context: Fixed tool-calling loops, turn closures, and thinking content-ordering.
|
| 6 |
+
#}
|
| 7 |
+
{%- macro format_parameters(properties, required, filter_keys=false) -%}
|
| 8 |
+
{%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
|
| 9 |
+
{%- set ns = namespace(found_first=false) -%}
|
| 10 |
+
{%- for key, value in properties | dictsort -%}
|
| 11 |
+
{%- set add_comma = false -%}
|
| 12 |
+
{%- if not filter_keys or key not in standard_keys -%}
|
| 13 |
+
{%- if ns.found_first %},{% endif -%}
|
| 14 |
+
{%- set ns.found_first = true -%}
|
| 15 |
+
{{ key }}:{
|
| 16 |
+
{%- if value['description'] -%}
|
| 17 |
+
description:<|"|>{{ value['description'] }}<|"|>
|
| 18 |
+
{%- set add_comma = true -%}
|
| 19 |
+
{%- endif -%}
|
| 20 |
+
{%- if value['type'] | upper == 'STRING' -%}
|
| 21 |
+
{%- if value['enum'] -%}
|
| 22 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 23 |
+
enum:{{ format_argument(value['enum']) }}
|
| 24 |
+
{%- endif -%}
|
| 25 |
+
{%- elif value['type'] | upper == 'ARRAY' -%}
|
| 26 |
+
{%- if value['items'] is mapping and value['items'] -%}
|
| 27 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 28 |
+
items:{
|
| 29 |
+
{%- set ns_items = namespace(found_first=false) -%}
|
| 30 |
+
{%- for item_key, item_value in value['items'] | dictsort -%}
|
| 31 |
+
{%- if item_value is not none -%}
|
| 32 |
+
{%- if ns_items.found_first %},{% endif -%}
|
| 33 |
+
{%- set ns_items.found_first = true -%}
|
| 34 |
+
{%- if item_key == 'properties' -%}
|
| 35 |
+
properties:{
|
| 36 |
+
{%- if item_value is mapping -%}
|
| 37 |
+
{{- format_parameters(item_value, value['items']['required'] | default([])) -}}
|
| 38 |
+
{%- endif -%}
|
| 39 |
+
}
|
| 40 |
+
{%- elif item_key == 'required' -%}
|
| 41 |
+
required:[
|
| 42 |
+
{%- for req_item in item_value -%}
|
| 43 |
+
<|"|>{{- req_item -}}<|"|>
|
| 44 |
+
{%- if not loop.last %},{% endif -%}
|
| 45 |
+
{%- endfor -%}
|
| 46 |
+
]
|
| 47 |
+
{%- elif item_key == 'type' -%}
|
| 48 |
+
{%- if item_value is string -%}
|
| 49 |
+
type:{{ format_argument(item_value | upper) }}
|
| 50 |
+
{%- else -%}
|
| 51 |
+
type:{{ format_argument(item_value | map('upper') | list) }}
|
| 52 |
+
{%- endif -%}
|
| 53 |
+
{%- else -%}
|
| 54 |
+
{{ item_key }}:{{ format_argument(item_value) }}
|
| 55 |
+
{%- endif -%}
|
| 56 |
+
{%- endif -%}
|
| 57 |
+
{%- endfor -%}
|
| 58 |
+
}
|
| 59 |
+
{%- endif -%}
|
| 60 |
+
{%- endif -%}
|
| 61 |
+
{%- if value['nullable'] %}
|
| 62 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 63 |
+
nullable:true
|
| 64 |
+
{%- endif -%}
|
| 65 |
+
{%- if value['type'] | upper == 'OBJECT' -%}
|
| 66 |
+
{%- if value['properties'] is defined and value['properties'] is mapping -%}
|
| 67 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 68 |
+
properties:{
|
| 69 |
+
{{- format_parameters(value['properties'], value['required'] | default([])) -}}
|
| 70 |
+
}
|
| 71 |
+
{%- elif value is mapping -%}
|
| 72 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 73 |
+
properties:{
|
| 74 |
+
{{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
|
| 75 |
+
}
|
| 76 |
+
{%- endif -%}
|
| 77 |
+
{%- if value['required'] -%}
|
| 78 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 79 |
+
required:[
|
| 80 |
+
{%- for item in value['required'] | default([]) -%}
|
| 81 |
+
<|"|>{{- item -}}<|"|>
|
| 82 |
+
{%- if not loop.last %},{% endif -%}
|
| 83 |
+
{%- endfor -%}
|
| 84 |
+
]
|
| 85 |
+
{%- endif -%}
|
| 86 |
+
{%- endif -%}
|
| 87 |
+
{%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
|
| 88 |
+
type:<|"|>{{ value['type'] | upper }}<|"|>}
|
| 89 |
+
{%- endif -%}
|
| 90 |
+
{%- endfor -%}
|
| 91 |
+
{%- endmacro -%}
|
| 92 |
+
{%- macro format_function_declaration(tool_data) -%}
|
| 93 |
+
declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
|
| 94 |
+
{%- set params = tool_data['function']['parameters'] -%}
|
| 95 |
+
{%- if params -%}
|
| 96 |
+
,parameters:{
|
| 97 |
+
{%- if params['properties'] -%}
|
| 98 |
+
properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
|
| 99 |
+
{%- endif -%}
|
| 100 |
+
{%- if params['required'] -%}
|
| 101 |
+
required:[
|
| 102 |
+
{%- for item in params['required'] -%}
|
| 103 |
+
<|"|>{{- item -}}<|"|>
|
| 104 |
+
{{- ',' if not loop.last -}}
|
| 105 |
+
{%- endfor -%}
|
| 106 |
+
],
|
| 107 |
+
{%- endif -%}
|
| 108 |
+
{%- if params['type'] -%}
|
| 109 |
+
type:<|"|>{{- params['type'] | upper -}}<|"|>}
|
| 110 |
+
{%- endif -%}
|
| 111 |
+
{%- endif -%}
|
| 112 |
+
{%- if 'response' in tool_data['function'] -%}
|
| 113 |
+
{%- set response_declaration = tool_data['function']['response'] -%}
|
| 114 |
+
,response:{
|
| 115 |
+
{%- if response_declaration['description'] -%}
|
| 116 |
+
description:<|"|>{{- response_declaration['description'] -}}<|"|>,
|
| 117 |
+
{%- endif -%}
|
| 118 |
+
{%- if response_declaration['type'] | upper == 'OBJECT' -%}
|
| 119 |
+
type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
|
| 120 |
+
{%- endif -%}
|
| 121 |
+
{%- endif -%}
|
| 122 |
+
}
|
| 123 |
+
{%- endmacro -%}
|
| 124 |
+
{%- macro format_argument(argument, escape_keys=True) -%}
|
| 125 |
+
{%- if argument is none -%}
|
| 126 |
+
{{- 'null' -}}
|
| 127 |
+
{%- elif argument is string -%}
|
| 128 |
+
{{- '<|"|>' + argument + '<|"|>' -}}
|
| 129 |
+
{%- elif argument is boolean -%}
|
| 130 |
+
{{- 'true' if argument else 'false' -}}
|
| 131 |
+
{%- elif argument is mapping -%}
|
| 132 |
+
{{- '{' -}}
|
| 133 |
+
{%- set ns = namespace(found_first=false) -%}
|
| 134 |
+
{%- for key, value in argument | dictsort -%}
|
| 135 |
+
{%- if ns.found_first %},{% endif -%}
|
| 136 |
+
{%- set ns.found_first = true -%}
|
| 137 |
+
{%- if escape_keys -%}
|
| 138 |
+
{{- '<|"|>' + key + '<|"|>' -}}
|
| 139 |
+
{%- else -%}
|
| 140 |
+
{{- key -}}
|
| 141 |
+
{%- endif -%}
|
| 142 |
+
:{{- format_argument(value, escape_keys=escape_keys) -}}
|
| 143 |
+
{%- endfor -%}
|
| 144 |
+
{{- '}' -}}
|
| 145 |
+
{%- elif argument is sequence -%}
|
| 146 |
+
{{- '[' -}}
|
| 147 |
+
{%- for item in argument -%}
|
| 148 |
+
{{- format_argument(item, escape_keys=escape_keys) -}}
|
| 149 |
+
{%- if not loop.last %},{% endif -%}
|
| 150 |
+
{%- endfor -%}
|
| 151 |
+
{{- ']' -}}
|
| 152 |
+
{%- else -%}
|
| 153 |
+
{{- argument -}}
|
| 154 |
+
{%- endif -%}
|
| 155 |
+
{%- endmacro -%}
|
| 156 |
+
{%- macro strip_thinking(text) -%}
|
| 157 |
+
{%- set ns = namespace(result='') -%}
|
| 158 |
+
{%- for part in text.split('<channel|>') -%}
|
| 159 |
+
{%- if '<|channel>' in part -%}
|
| 160 |
+
{%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
|
| 161 |
+
{%- else -%}
|
| 162 |
+
{%- set ns.result = ns.result + part -%}
|
| 163 |
+
{%- endif -%}
|
| 164 |
+
{%- endfor -%}
|
| 165 |
+
{{- ns.result | trim -}}
|
| 166 |
+
{%- endmacro -%}
|
| 167 |
+
|
| 168 |
+
{%- macro format_tool_response_block(tool_name, response) -%}
|
| 169 |
+
{{- '<|tool_response>' -}}
|
| 170 |
+
{%- if response is mapping -%}
|
| 171 |
+
{{- 'response:' + tool_name + '{' -}}
|
| 172 |
+
{%- for key, value in response | dictsort -%}
|
| 173 |
+
{{- key -}}:{{- format_argument(value, escape_keys=False) -}}
|
| 174 |
+
{%- if not loop.last %},{% endif -%}
|
| 175 |
+
{%- endfor -%}
|
| 176 |
+
{{- '}' -}}
|
| 177 |
+
{%- else -%}
|
| 178 |
+
{{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
|
| 179 |
+
{%- endif -%}
|
| 180 |
+
{{- '<tool_response|>' -}}
|
| 181 |
+
{%- endmacro -%}
|
| 182 |
+
|
| 183 |
+
{#- ===== SETUP ===== -#}
|
| 184 |
+
{%- set ns = namespace(prev_message_type=None, prev_non_tool_role=None) -%}
|
| 185 |
+
{%- set loop_messages = messages -%}
|
| 186 |
+
{%- set enable_thinking = enable_thinking | default(false) -%}
|
| 187 |
+
{%- set preserve_thinking = preserve_thinking | default(false) -%}
|
| 188 |
+
{{- bos_token -}}
|
| 189 |
+
{#- Handle System/Tool Definitions Block -#}
|
| 190 |
+
{%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}
|
| 191 |
+
{{- '<|turn>system\n' -}}
|
| 192 |
+
{#- Inject Thinking token at the very top of the FIRST system turn -#}
|
| 193 |
+
{%- if enable_thinking -%}
|
| 194 |
+
{{- '<|think|>\n' -}}
|
| 195 |
+
{%- set ns.prev_message_type = 'think' -%}
|
| 196 |
+
{%- endif -%}
|
| 197 |
+
{%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
|
| 198 |
+
{%- if messages[0]['content'] is string -%}
|
| 199 |
+
{{- messages[0]['content'] | trim -}}
|
| 200 |
+
{%- elif messages[0]['content'] is sequence -%}
|
| 201 |
+
{%- for item in messages[0]['content'] -%}
|
| 202 |
+
{{- item['text'] | trim + ' '-}}
|
| 203 |
+
{%- endfor -%}
|
| 204 |
+
{%- endif -%}
|
| 205 |
+
{%- set loop_messages = messages[1:] -%}
|
| 206 |
+
{%- endif -%}
|
| 207 |
+
{%- if tools -%}
|
| 208 |
+
{%- for tool in tools %}
|
| 209 |
+
{{- '<|tool>' -}}
|
| 210 |
+
{{- format_function_declaration(tool) | trim -}}
|
| 211 |
+
{{- '<tool|>' -}}
|
| 212 |
+
{%- endfor %}
|
| 213 |
+
{%- set ns.prev_message_type = 'tool' -%}
|
| 214 |
+
{%- endif -%}
|
| 215 |
+
{{- '<turn|>\n' -}}
|
| 216 |
+
{%- endif %}
|
| 217 |
+
|
| 218 |
+
{#- Pre-scan: find last user message index for reasoning guard -#}
|
| 219 |
+
{%- set ns_turn = namespace(last_user_idx=-1) -%}
|
| 220 |
+
{%- for i in range(loop_messages | length) -%}
|
| 221 |
+
{%- if loop_messages[i]['role'] == 'user' -%}
|
| 222 |
+
{%- set ns_turn.last_user_idx = i -%}
|
| 223 |
+
{%- endif -%}
|
| 224 |
+
{%- endfor -%}
|
| 225 |
+
|
| 226 |
+
{#- Loop through messages -#}
|
| 227 |
+
{%- for message in loop_messages -%}
|
| 228 |
+
{%- if message['role'] != 'tool' -%}
|
| 229 |
+
{%- set ns.prev_message_type = None -%}
|
| 230 |
+
{%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
|
| 231 |
+
{#- Detect continuation using tracked state — O(1) instead of O(n) backward scan -#}
|
| 232 |
+
{%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}
|
| 233 |
+
{%- if not continue_same_model_turn -%}
|
| 234 |
+
{{- '<|turn>' + role + '\n' }}
|
| 235 |
+
|
| 236 |
+
{%- endif -%}
|
| 237 |
+
|
| 238 |
+
{#- Render reasoning/reasoning_content as thinking channel -#}
|
| 239 |
+
{%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
|
| 240 |
+
{%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}
|
| 241 |
+
{%- if thinking_text and thinking_gate -%}
|
| 242 |
+
{{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
|
| 243 |
+
{%- endif -%}
|
| 244 |
+
|
| 245 |
+
{%- if message.get('tool_calls') -%}
|
| 246 |
+
{%- for tool_call in message.get('tool_calls') -%}
|
| 247 |
+
{%- set function = tool_call['function'] -%}
|
| 248 |
+
{{- '<|tool_call>call:' + function['name'] + '{' -}}
|
| 249 |
+
{%- if function['arguments'] is mapping -%}
|
| 250 |
+
{%- set ns_args = namespace(found_first=false) -%}
|
| 251 |
+
{%- for key, value in function['arguments'] | dictsort -%}
|
| 252 |
+
{%- if ns_args.found_first %},{% endif -%}
|
| 253 |
+
{%- set ns_args.found_first = true -%}
|
| 254 |
+
{{- key -}}:{{- format_argument(value, escape_keys=False) -}}
|
| 255 |
+
{%- endfor -%}
|
| 256 |
+
{%- elif function['arguments'] is none -%}
|
| 257 |
+
{%- else -%}
|
| 258 |
+
{{- raise_exception(
|
| 259 |
+
"chat_template: tool_calls[].function.arguments must be a "
|
| 260 |
+
"JSON object (mapping), not a string. Deserialize arguments "
|
| 261 |
+
"before passing to the template."
|
| 262 |
+
) -}}
|
| 263 |
+
{%- endif -%}
|
| 264 |
+
{{- '}<tool_call|>' -}}
|
| 265 |
+
{%- endfor -%}
|
| 266 |
+
{%- set ns.prev_message_type = 'tool_call' -%}
|
| 267 |
+
{%- endif -%}
|
| 268 |
+
|
| 269 |
+
{%- set ns_tr_out = namespace(flag=false) -%}
|
| 270 |
+
{%- if message.get('tool_responses') -%}
|
| 271 |
+
{#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
|
| 272 |
+
{%- for tool_response in message.get('tool_responses') -%}
|
| 273 |
+
{{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}
|
| 274 |
+
{%- set ns_tr_out.flag = true -%}
|
| 275 |
+
{%- set ns.prev_message_type = 'tool_response' -%}
|
| 276 |
+
{%- endfor -%}
|
| 277 |
+
{%- elif message.get('tool_calls') -%}
|
| 278 |
+
{#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
|
| 279 |
+
{%- set ns_tool_scan = namespace(stopped=false) -%}
|
| 280 |
+
{%- for k in range(loop.index0 + 1, loop_messages | length) -%}
|
| 281 |
+
{%- if ns_tool_scan.stopped -%}
|
| 282 |
+
{%- elif loop_messages[k]['role'] != 'tool' -%}
|
| 283 |
+
{%- set ns_tool_scan.stopped = true -%}
|
| 284 |
+
{%- else -%}
|
| 285 |
+
{%- set follow = loop_messages[k] -%}
|
| 286 |
+
{#- Resolve tool_call_id to function name -#}
|
| 287 |
+
{%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}
|
| 288 |
+
{%- for tc in message.get('tool_calls') -%}
|
| 289 |
+
{%- if tc.get('id') == follow.get('tool_call_id') -%}
|
| 290 |
+
{%- set ns_tname.name = tc['function']['name'] -%}
|
| 291 |
+
{%- endif -%}
|
| 292 |
+
{%- endfor -%}
|
| 293 |
+
{#- Handle content as string or content-parts array -#}
|
| 294 |
+
{%- set tool_body = follow.get('content') -%}
|
| 295 |
+
{%- if tool_body is string -%}
|
| 296 |
+
{{- format_tool_response_block(ns_tname.name, tool_body) -}}
|
| 297 |
+
{%- elif tool_body is sequence and tool_body is not string -%}
|
| 298 |
+
{%- set ns_txt = namespace(s='') -%}
|
| 299 |
+
{%- for part in tool_body -%}
|
| 300 |
+
{%- if part.get('type') == 'text' -%}
|
| 301 |
+
{%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
|
| 302 |
+
{%- endif -%}
|
| 303 |
+
{%- endfor -%}
|
| 304 |
+
{{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
|
| 305 |
+
{%- for part in tool_body -%}
|
| 306 |
+
{%- if part.get('type') in ['image', 'image_url'] -%}
|
| 307 |
+
{{- '<|image|>' -}}
|
| 308 |
+
{%- elif part.get('type') in ['audio', 'input_audio'] -%}
|
| 309 |
+
{{- '<|audio|>' -}}
|
| 310 |
+
{%- elif part.get('type') == 'video' -%}
|
| 311 |
+
{{- '<|video|>' -}}
|
| 312 |
+
{%- endif -%}
|
| 313 |
+
{%- endfor -%}
|
| 314 |
+
{%- else -%}
|
| 315 |
+
{{- format_tool_response_block(ns_tname.name, tool_body) -}}
|
| 316 |
+
{%- endif -%}
|
| 317 |
+
{%- set ns_tr_out.flag = true -%}
|
| 318 |
+
{%- set ns.prev_message_type = 'tool_response' -%}
|
| 319 |
+
{%- endif -%}
|
| 320 |
+
{%- endfor -%}
|
| 321 |
+
{%- endif -%}
|
| 322 |
+
|
| 323 |
+
{%- set captured_content -%}
|
| 324 |
+
{%- if message.get('content') is string -%}
|
| 325 |
+
{%- if role == 'model' -%}
|
| 326 |
+
{{- strip_thinking(message['content']) -}}
|
| 327 |
+
{%- else -%}
|
| 328 |
+
{{- message['content'] | trim -}}
|
| 329 |
+
{%- endif -%}
|
| 330 |
+
{%- elif message.get('content') is sequence -%}
|
| 331 |
+
{%- for item in message['content'] -%}
|
| 332 |
+
{%- if item.get('type') == 'text' -%}
|
| 333 |
+
{%- if role == 'model' -%}
|
| 334 |
+
{{- strip_thinking(item['text']) -}}
|
| 335 |
+
{%- else -%}
|
| 336 |
+
{{- item['text'] | trim -}}
|
| 337 |
+
{%- endif -%}
|
| 338 |
+
{%- elif item.get('type') in ['image', 'image_url'] -%}
|
| 339 |
+
{{- '<|image|>' -}}
|
| 340 |
+
{%- elif item.get('type') in ['audio', 'input_audio'] -%}
|
| 341 |
+
{{- '<|audio|>' -}}
|
| 342 |
+
{%- elif item.get('type') == 'video' -%}
|
| 343 |
+
{{- '<|video|>' -}}
|
| 344 |
+
{%- endif -%}
|
| 345 |
+
{%- endfor -%}
|
| 346 |
+
{%- endif -%}
|
| 347 |
+
{%- endset -%}
|
| 348 |
+
|
| 349 |
+
{{- captured_content -}}
|
| 350 |
+
{%- set has_content = captured_content | trim | length > 0 -%}
|
| 351 |
+
|
| 352 |
+
{#- Forward-scan: find next non-tool message role for continuation detection -#}
|
| 353 |
+
{%- set next_nt = namespace(role=None, found=false) -%}
|
| 354 |
+
{%- for j in range(loop.index0 + 1, loop_messages | length) -%}
|
| 355 |
+
{%- if not next_nt.found -%}
|
| 356 |
+
{%- if loop_messages[j]['role'] != 'tool' -%}
|
| 357 |
+
{%- set next_nt.role = loop_messages[j]['role'] -%}
|
| 358 |
+
{%- set next_nt.found = true -%}
|
| 359 |
+
{%- endif -%}
|
| 360 |
+
{%- endif -%}
|
| 361 |
+
{%- endfor -%}
|
| 362 |
+
|
| 363 |
+
{%- set continues_into_next = (
|
| 364 |
+
role == 'model'
|
| 365 |
+
and next_nt.role == 'assistant'
|
| 366 |
+
and (not message.get('tool_calls') or ns_tr_out.flag)
|
| 367 |
+
) -%}
|
| 368 |
+
|
| 369 |
+
{%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
|
| 370 |
+
{{- '<|tool_response>' -}}
|
| 371 |
+
{%- elif continues_into_next -%}
|
| 372 |
+
{%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}
|
| 373 |
+
{{- '<turn|>\n' -}}
|
| 374 |
+
{%- endif -%}
|
| 375 |
+
|
| 376 |
+
{#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}
|
| 377 |
+
{%- set ns.prev_non_tool_role = message['role'] -%}
|
| 378 |
+
{%- endif -%}
|
| 379 |
+
{%- endfor -%}
|
| 380 |
+
|
| 381 |
+
{%- if add_generation_prompt -%}
|
| 382 |
+
{%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
|
| 383 |
+
{{- '<|turn>model\n' -}}
|
| 384 |
+
{%- if not enable_thinking -%}
|
| 385 |
+
{{- '<|channel>thought\n<channel|>' -}}
|
| 386 |
+
{%- endif -%}
|
| 387 |
+
{%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}
|
| 388 |
+
{{- '<|channel>thought\n' -}}
|
| 389 |
+
{%- endif -%}
|
| 390 |
+
{%- endif -%}
|
adapter/tokenizer_config.json
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_token": "<|audio|>",
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"boa_token": "<|audio>",
|
| 5 |
+
"boi_token": "<|image>",
|
| 6 |
+
"bos_token": "<bos>",
|
| 7 |
+
"eoa_token": "<audio|>",
|
| 8 |
+
"eoc_token": "<channel|>",
|
| 9 |
+
"eoi_token": "<image|>",
|
| 10 |
+
"eos_token": "<eos>",
|
| 11 |
+
"eot_token": "<turn|>",
|
| 12 |
+
"escape_token": "<|\"|>",
|
| 13 |
+
"etc_token": "<tool_call|>",
|
| 14 |
+
"etd_token": "<tool|>",
|
| 15 |
+
"etr_token": "<tool_response|>",
|
| 16 |
+
"extra_special_tokens": [
|
| 17 |
+
"<|video|>"
|
| 18 |
+
],
|
| 19 |
+
"image_token": "<|image|>",
|
| 20 |
+
"is_local": true,
|
| 21 |
+
"local_files_only": true,
|
| 22 |
+
"mask_token": "<mask>",
|
| 23 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 24 |
+
"model_specific_special_tokens": {
|
| 25 |
+
"audio_token": "<|audio|>",
|
| 26 |
+
"boa_token": "<|audio>",
|
| 27 |
+
"boi_token": "<|image>",
|
| 28 |
+
"eoa_token": "<audio|>",
|
| 29 |
+
"eoc_token": "<channel|>",
|
| 30 |
+
"eoi_token": "<image|>",
|
| 31 |
+
"eot_token": "<turn|>",
|
| 32 |
+
"escape_token": "<|\"|>",
|
| 33 |
+
"etc_token": "<tool_call|>",
|
| 34 |
+
"etd_token": "<tool|>",
|
| 35 |
+
"etr_token": "<tool_response|>",
|
| 36 |
+
"image_token": "<|image|>",
|
| 37 |
+
"soc_token": "<|channel>",
|
| 38 |
+
"sot_token": "<|turn>",
|
| 39 |
+
"stc_token": "<|tool_call>",
|
| 40 |
+
"std_token": "<|tool>",
|
| 41 |
+
"str_token": "<|tool_response>",
|
| 42 |
+
"think_token": "<|think|>"
|
| 43 |
+
},
|
| 44 |
+
"pad_token": "<pad>",
|
| 45 |
+
"padding_side": "right",
|
| 46 |
+
"processor_class": "Gemma4UnifiedProcessor",
|
| 47 |
+
"soc_token": "<|channel>",
|
| 48 |
+
"sot_token": "<|turn>",
|
| 49 |
+
"stc_token": "<|tool_call>",
|
| 50 |
+
"std_token": "<|tool>",
|
| 51 |
+
"str_token": "<|tool_response>",
|
| 52 |
+
"think_token": "<|think|>",
|
| 53 |
+
"tokenizer_class": "GemmaTokenizer",
|
| 54 |
+
"unk_token": "<unk>",
|
| 55 |
+
"added_tokens_decoder": {
|
| 56 |
+
"0": {
|
| 57 |
+
"content": "<pad>",
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"lstrip": false,
|
| 60 |
+
"rstrip": false,
|
| 61 |
+
"normalized": false,
|
| 62 |
+
"special": true
|
| 63 |
+
},
|
| 64 |
+
"1": {
|
| 65 |
+
"content": "<eos>",
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"lstrip": false,
|
| 68 |
+
"rstrip": false,
|
| 69 |
+
"normalized": false,
|
| 70 |
+
"special": true
|
| 71 |
+
},
|
| 72 |
+
"2": {
|
| 73 |
+
"content": "<bos>",
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"lstrip": false,
|
| 76 |
+
"rstrip": false,
|
| 77 |
+
"normalized": false,
|
| 78 |
+
"special": true
|
| 79 |
+
},
|
| 80 |
+
"3": {
|
| 81 |
+
"content": "<unk>",
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"lstrip": false,
|
| 84 |
+
"rstrip": false,
|
| 85 |
+
"normalized": false,
|
| 86 |
+
"special": true
|
| 87 |
+
},
|
| 88 |
+
"4": {
|
| 89 |
+
"content": "<mask>",
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"lstrip": false,
|
| 92 |
+
"rstrip": false,
|
| 93 |
+
"normalized": false,
|
| 94 |
+
"special": true
|
| 95 |
+
},
|
| 96 |
+
"46": {
|
| 97 |
+
"content": "<|tool>",
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"lstrip": false,
|
| 100 |
+
"rstrip": false,
|
| 101 |
+
"normalized": false,
|
| 102 |
+
"special": true
|
| 103 |
+
},
|
| 104 |
+
"47": {
|
| 105 |
+
"content": "<tool|>",
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"lstrip": false,
|
| 108 |
+
"rstrip": false,
|
| 109 |
+
"normalized": false,
|
| 110 |
+
"special": true
|
| 111 |
+
},
|
| 112 |
+
"48": {
|
| 113 |
+
"content": "<|tool_call>",
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"lstrip": false,
|
| 116 |
+
"rstrip": false,
|
| 117 |
+
"normalized": false,
|
| 118 |
+
"special": true
|
| 119 |
+
},
|
| 120 |
+
"49": {
|
| 121 |
+
"content": "<tool_call|>",
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"lstrip": false,
|
| 124 |
+
"rstrip": false,
|
| 125 |
+
"normalized": false,
|
| 126 |
+
"special": true
|
| 127 |
+
},
|
| 128 |
+
"50": {
|
| 129 |
+
"content": "<|tool_response>",
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"lstrip": false,
|
| 132 |
+
"rstrip": false,
|
| 133 |
+
"normalized": false,
|
| 134 |
+
"special": true
|
| 135 |
+
},
|
| 136 |
+
"51": {
|
| 137 |
+
"content": "<tool_response|>",
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"lstrip": false,
|
| 140 |
+
"rstrip": false,
|
| 141 |
+
"normalized": false,
|
| 142 |
+
"special": true
|
| 143 |
+
},
|
| 144 |
+
"52": {
|
| 145 |
+
"content": "<|\"|>",
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"lstrip": false,
|
| 148 |
+
"rstrip": false,
|
| 149 |
+
"normalized": false,
|
| 150 |
+
"special": true
|
| 151 |
+
},
|
| 152 |
+
"98": {
|
| 153 |
+
"content": "<|think|>",
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"lstrip": false,
|
| 156 |
+
"rstrip": false,
|
| 157 |
+
"normalized": false,
|
| 158 |
+
"special": true
|
| 159 |
+
},
|
| 160 |
+
"100": {
|
| 161 |
+
"content": "<|channel>",
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"lstrip": false,
|
| 164 |
+
"rstrip": false,
|
| 165 |
+
"normalized": false,
|
| 166 |
+
"special": true
|
| 167 |
+
},
|
| 168 |
+
"101": {
|
| 169 |
+
"content": "<channel|>",
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"lstrip": false,
|
| 172 |
+
"rstrip": false,
|
| 173 |
+
"normalized": false,
|
| 174 |
+
"special": true
|
| 175 |
+
},
|
| 176 |
+
"105": {
|
| 177 |
+
"content": "<|turn>",
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"lstrip": false,
|
| 180 |
+
"rstrip": false,
|
| 181 |
+
"normalized": false,
|
| 182 |
+
"special": true
|
| 183 |
+
},
|
| 184 |
+
"106": {
|
| 185 |
+
"content": "<turn|>",
|
| 186 |
+
"single_word": false,
|
| 187 |
+
"lstrip": false,
|
| 188 |
+
"rstrip": false,
|
| 189 |
+
"normalized": false,
|
| 190 |
+
"special": true
|
| 191 |
+
},
|
| 192 |
+
"255999": {
|
| 193 |
+
"content": "<|image>",
|
| 194 |
+
"single_word": false,
|
| 195 |
+
"lstrip": false,
|
| 196 |
+
"rstrip": false,
|
| 197 |
+
"normalized": false,
|
| 198 |
+
"special": true
|
| 199 |
+
},
|
| 200 |
+
"256000": {
|
| 201 |
+
"content": "<|audio>",
|
| 202 |
+
"single_word": false,
|
| 203 |
+
"lstrip": false,
|
| 204 |
+
"rstrip": false,
|
| 205 |
+
"normalized": false,
|
| 206 |
+
"special": true
|
| 207 |
+
},
|
| 208 |
+
"258880": {
|
| 209 |
+
"content": "<|image|>",
|
| 210 |
+
"single_word": false,
|
| 211 |
+
"lstrip": false,
|
| 212 |
+
"rstrip": false,
|
| 213 |
+
"normalized": false,
|
| 214 |
+
"special": true
|
| 215 |
+
},
|
| 216 |
+
"258881": {
|
| 217 |
+
"content": "<|audio|>",
|
| 218 |
+
"single_word": false,
|
| 219 |
+
"lstrip": false,
|
| 220 |
+
"rstrip": false,
|
| 221 |
+
"normalized": false,
|
| 222 |
+
"special": true
|
| 223 |
+
},
|
| 224 |
+
"258882": {
|
| 225 |
+
"content": "<image|>",
|
| 226 |
+
"single_word": false,
|
| 227 |
+
"lstrip": false,
|
| 228 |
+
"rstrip": false,
|
| 229 |
+
"normalized": false,
|
| 230 |
+
"special": true
|
| 231 |
+
},
|
| 232 |
+
"258883": {
|
| 233 |
+
"content": "<audio|>",
|
| 234 |
+
"single_word": false,
|
| 235 |
+
"lstrip": false,
|
| 236 |
+
"rstrip": false,
|
| 237 |
+
"normalized": false,
|
| 238 |
+
"special": true
|
| 239 |
+
},
|
| 240 |
+
"258884": {
|
| 241 |
+
"content": "<|video|>",
|
| 242 |
+
"single_word": false,
|
| 243 |
+
"lstrip": false,
|
| 244 |
+
"rstrip": false,
|
| 245 |
+
"normalized": false,
|
| 246 |
+
"special": true
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
audio-test-result.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "chatcmpl-native-1786014995161",
|
| 3 |
+
"object": "chat.completion",
|
| 4 |
+
"created": 1786014995,
|
| 5 |
+
"model": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 6 |
+
"choices": [
|
| 7 |
+
{
|
| 8 |
+
"index": 0,
|
| 9 |
+
"message": {
|
| 10 |
+
"role": "assistant",
|
| 11 |
+
"content": "123\n\n\n\n"
|
| 12 |
+
},
|
| 13 |
+
"finish_reason": "length"
|
| 14 |
+
}
|
| 15 |
+
],
|
| 16 |
+
"usage": {
|
| 17 |
+
"prompt_tokens": 160,
|
| 18 |
+
"completion_tokens": 12,
|
| 19 |
+
"total_tokens": 172
|
| 20 |
+
},
|
| 21 |
+
"timing": {
|
| 22 |
+
"elapsed_seconds": 26.382
|
| 23 |
+
}
|
| 24 |
+
}
|
llama-bootstrap-verification.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"verified_at": "2026-08-06T12:25:53.153444+00:00",
|
| 4 |
+
"launcher": "Start-Llama-Service.bat",
|
| 5 |
+
"bootstrap": {
|
| 6 |
+
"state": "passed",
|
| 7 |
+
"missing_runtime_branch_executed": true,
|
| 8 |
+
"release": "b10293",
|
| 9 |
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
| 10 |
+
"assets": [
|
| 11 |
+
{
|
| 12 |
+
"name": "llama-b10293-bin-win-cuda-13.3-x64.zip",
|
| 13 |
+
"bytes": 146520044,
|
| 14 |
+
"digest": "sha256:3eb64cd39e698459846ae74383935fe50950c55548e49d6938e95b89aaabf652"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"name": "cudart-llama-bin-win-cuda-13.3-x64.zip",
|
| 18 |
+
"bytes": 390970417,
|
| 19 |
+
"digest": "sha256:1462a050eb4c684921ba51dcc4cc488a036674c3e73e9945ee705b854808d03e"
|
| 20 |
+
}
|
| 21 |
+
],
|
| 22 |
+
"runtime_files": 55,
|
| 23 |
+
"runtime_bytes": 701273776,
|
| 24 |
+
"fallback_source": "official https://github.com/ggml-org/llama.cpp/releases/latest"
|
| 25 |
+
},
|
| 26 |
+
"llama_service": {
|
| 27 |
+
"build": "b10293 / a1f96d4fc",
|
| 28 |
+
"backend": "llama.cpp CUDA 13.3",
|
| 29 |
+
"model": "Gemma4 12B base Q5_K_M GGUF",
|
| 30 |
+
"base_sha256": "057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752",
|
| 31 |
+
"lora": "final step 03829 Q8_0 GGUF",
|
| 32 |
+
"lora_sha256": "3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037",
|
| 33 |
+
"lora_scale_requested": 0.45,
|
| 34 |
+
"lora_scale_live": 0.44999998807907104,
|
| 35 |
+
"context_tokens": 131072,
|
| 36 |
+
"web_ui": "http://127.0.0.1:8091",
|
| 37 |
+
"prompt_embedding": "llama-model/chat_template.prompt30k.jinja",
|
| 38 |
+
"prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 39 |
+
"native_multimodal_launcher": "start.ps1"
|
| 40 |
+
},
|
| 41 |
+
"variant_tests": {
|
| 42 |
+
"full-bf16": {
|
| 43 |
+
"state": "passed",
|
| 44 |
+
"command": "POST /v1/chat/completions without caller system message",
|
| 45 |
+
"exit_status": 0,
|
| 46 |
+
"wall_seconds": 14.787,
|
| 47 |
+
"literal_output": "我是安若,一个跨性别友好、性别多元友好、创伤知情、尊重自主权和生命安全的人工智能陪伴助手。",
|
| 48 |
+
"prompt_tokens": 25147,
|
| 49 |
+
"completion_tokens": 31,
|
| 50 |
+
"prompt_tokens_per_second": 1790.8100048646054,
|
| 51 |
+
"context_tokens": 131072,
|
| 52 |
+
"lora_scale": 0.44999998807907104,
|
| 53 |
+
"caller_system_message": "absent; 30k prompt came from embedded Jinja template"
|
| 54 |
+
},
|
| 55 |
+
"quantized-nf4": {
|
| 56 |
+
"state": "passed",
|
| 57 |
+
"command": ".\\llama-bootstrap.ps1 -Port 8091 -Context 131072 -NoBrowser -HiddenServer",
|
| 58 |
+
"exit_status": 0,
|
| 59 |
+
"wall_seconds": 14.451,
|
| 60 |
+
"literal_output": "我是安若,一个跨性别友好、性别多元友好、创伤知情、尊重自主权和生命安全的人工智能陪伴助手。",
|
| 61 |
+
"prompt_tokens": 25147,
|
| 62 |
+
"completion_tokens": 31,
|
| 63 |
+
"prompt_tokens_per_second": 1832.645043830949,
|
| 64 |
+
"context_tokens": 131072,
|
| 65 |
+
"lora_scale": 0.44999998807907104,
|
| 66 |
+
"caller_system_message": "absent; 30k prompt came from embedded Jinja template"
|
| 67 |
+
}
|
| 68 |
+
},
|
| 69 |
+
"rollback": {
|
| 70 |
+
"state": "passed",
|
| 71 |
+
"tested_at": "2026-08-06T12:24:11.1435208Z",
|
| 72 |
+
"records": [
|
| 73 |
+
{
|
| 74 |
+
"variant": "full-bf16",
|
| 75 |
+
"fixture": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\reports\\llama-rollback-fixture-full-bf16",
|
| 76 |
+
"command": ".\\rollback-llama-bootstrap.ps1",
|
| 77 |
+
"exit_status": 0,
|
| 78 |
+
"literal_output": "package llama bootstrap rolled back to the verified native-only baseline",
|
| 79 |
+
"baseline_restored": true,
|
| 80 |
+
"assets_moved_to_disabled": true
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"variant": "quantized-nf4",
|
| 84 |
+
"fixture": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\reports\\llama-rollback-fixture-quantized-nf4",
|
| 85 |
+
"command": ".\\rollback-llama-bootstrap.ps1",
|
| 86 |
+
"exit_status": 0,
|
| 87 |
+
"literal_output": "package llama bootstrap rolled back to the verified native-only baseline",
|
| 88 |
+
"baseline_restored": true,
|
| 89 |
+
"assets_moved_to_disabled": true
|
| 90 |
+
}
|
| 91 |
+
],
|
| 92 |
+
"root_rollback_parse": "passed"
|
| 93 |
+
},
|
| 94 |
+
"package_variant": "full-bf16"
|
| 95 |
+
}
|
llama-bootstrap.ps1
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[CmdletBinding()]
|
| 2 |
+
param(
|
| 3 |
+
[int]$Port = 8091,
|
| 4 |
+
[int]$Context = 131072,
|
| 5 |
+
[switch]$InstallOnly,
|
| 6 |
+
[switch]$NoBrowser,
|
| 7 |
+
[switch]$HiddenServer
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
$ErrorActionPreference = 'Stop'
|
| 11 |
+
$ProgressPreference = 'Continue'
|
| 12 |
+
$Package = $PSScriptRoot
|
| 13 |
+
$ModelDir = Join-Path $Package 'llama-model'
|
| 14 |
+
$BaseModel = Join-Path $ModelDir 'anru-gemma4-12b-base-Q5_K_M.gguf'
|
| 15 |
+
$LoraModel = Join-Path $ModelDir 'anru-final-lora-Q8_0.gguf'
|
| 16 |
+
$ChatTemplate = Join-Path $ModelDir 'chat_template.prompt30k.jinja'
|
| 17 |
+
$UiConfig = Join-Path $ModelDir 'ui-config.json'
|
| 18 |
+
$RuntimeManifest = Join-Path $Package 'llama-runtime-state.json'
|
| 19 |
+
$ModelVerification = Join-Path $ModelDir 'model-verification.json'
|
| 20 |
+
|
| 21 |
+
function Get-LlamaServer {
|
| 22 |
+
$Candidates = @(Get-ChildItem -LiteralPath $Package -Directory -Filter 'llama-runtime*' -ErrorAction SilentlyContinue |
|
| 23 |
+
Sort-Object @{ Expression = { if ($_.Name -eq 'llama-runtime') { 0 } else { 1 } } }, Name)
|
| 24 |
+
foreach ($Directory in $Candidates) {
|
| 25 |
+
$Found = Get-ChildItem -LiteralPath $Directory.FullName -Recurse -File -Filter 'llama-server.exe' -ErrorAction SilentlyContinue |
|
| 26 |
+
Select-Object -First 1
|
| 27 |
+
if ($Found) { return $Found.FullName }
|
| 28 |
+
}
|
| 29 |
+
return $null
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
function Install-LlamaRuntime {
|
| 33 |
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
| 34 |
+
Write-Host '[bootstrap] llama.cpp runtime is missing; resolving the latest official Windows release...'
|
| 35 |
+
$Headers = @{ 'User-Agent' = 'Anru-Llama-Bootstrap' }
|
| 36 |
+
$Release = Invoke-RestMethod -Uri 'https://api.github.com/repos/ggml-org/llama.cpp/releases/latest' `
|
| 37 |
+
-Headers $Headers -TimeoutSec 60
|
| 38 |
+
|
| 39 |
+
$GpuName = ''
|
| 40 |
+
$Nvidia = Get-Command 'nvidia-smi.exe' -ErrorAction SilentlyContinue
|
| 41 |
+
if ($Nvidia) {
|
| 42 |
+
$GpuName = (& $Nvidia.Source --query-gpu=name --format=csv,noheader 2>$null | Select-Object -First 1).Trim()
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
$Assets = @()
|
| 46 |
+
if ($GpuName) {
|
| 47 |
+
$CudaFlavor = if ($GpuName -match '(?i)RTX\s*50\d\d|Blackwell') { '13.3' } else { '12.4' }
|
| 48 |
+
$Main = $Release.assets | Where-Object { $_.name -match "^llama-.*-bin-win-cuda-$([regex]::Escape($CudaFlavor))-x64\.zip$" } | Select-Object -First 1
|
| 49 |
+
$Cuda = $Release.assets | Where-Object { $_.name -eq "cudart-llama-bin-win-cuda-$CudaFlavor-x64.zip" } | Select-Object -First 1
|
| 50 |
+
if (-not $Main -or -not $Cuda) { throw "No matching CUDA $CudaFlavor Windows assets in release $($Release.tag_name)" }
|
| 51 |
+
$Assets = @($Main, $Cuda)
|
| 52 |
+
Write-Host "[bootstrap] detected $GpuName; installing CUDA $CudaFlavor build $($Release.tag_name)."
|
| 53 |
+
} else {
|
| 54 |
+
$Main = $Release.assets | Where-Object { $_.name -match '^llama-.*-bin-win-cpu-x64\.zip$' } | Select-Object -First 1
|
| 55 |
+
if (-not $Main) { throw "No Windows CPU asset in release $($Release.tag_name)" }
|
| 56 |
+
$Assets = @($Main)
|
| 57 |
+
Write-Host "[bootstrap] NVIDIA GPU not detected; installing CPU build $($Release.tag_name)."
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
$TempRoot = Join-Path ([IO.Path]::GetTempPath()) ("AnruLlamaBootstrap-" + [guid]::NewGuid().ToString('N'))
|
| 61 |
+
$RuntimeDir = Join-Path $Package 'llama-runtime'
|
| 62 |
+
if (Test-Path -LiteralPath $RuntimeDir) {
|
| 63 |
+
$RuntimeDir = Join-Path $Package ("llama-runtime-" + $Release.tag_name)
|
| 64 |
+
}
|
| 65 |
+
New-Item -ItemType Directory -Path $TempRoot, $RuntimeDir -Force | Out-Null
|
| 66 |
+
try {
|
| 67 |
+
$Index = 0
|
| 68 |
+
$InstalledAssets = @()
|
| 69 |
+
foreach ($Asset in $Assets) {
|
| 70 |
+
$Index++
|
| 71 |
+
$Zip = Join-Path $TempRoot $Asset.name
|
| 72 |
+
$Extract = Join-Path $TempRoot ("extract-$Index")
|
| 73 |
+
Write-Host "[bootstrap] downloading $($Asset.name) ($([math]::Round($Asset.size / 1MB, 1)) MiB)..."
|
| 74 |
+
Invoke-WebRequest -Uri $Asset.browser_download_url -Headers $Headers -OutFile $Zip -UseBasicParsing -TimeoutSec 1800
|
| 75 |
+
if ($Asset.digest -and $Asset.digest -match '^sha256:(.+)$') {
|
| 76 |
+
$Actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $Zip).Hash
|
| 77 |
+
if ($Actual -ne $Matches[1].ToUpperInvariant()) { throw "Download checksum mismatch: $($Asset.name)" }
|
| 78 |
+
}
|
| 79 |
+
Expand-Archive -LiteralPath $Zip -DestinationPath $Extract -Force
|
| 80 |
+
foreach ($File in Get-ChildItem -LiteralPath $Extract -Recurse -File) {
|
| 81 |
+
Copy-Item -LiteralPath $File.FullName -Destination (Join-Path $RuntimeDir $File.Name) -Force
|
| 82 |
+
}
|
| 83 |
+
$InstalledAssets += [ordered]@{ name = $Asset.name; bytes = [long]$Asset.size; digest = $Asset.digest }
|
| 84 |
+
}
|
| 85 |
+
$Server = Get-ChildItem -LiteralPath $RuntimeDir -Recurse -File -Filter 'llama-server.exe' | Select-Object -First 1
|
| 86 |
+
if (-not $Server) { throw 'Downloaded runtime does not contain llama-server.exe' }
|
| 87 |
+
[ordered]@{
|
| 88 |
+
state = 'installed'
|
| 89 |
+
release = [string]$Release.tag_name
|
| 90 |
+
gpu = $GpuName
|
| 91 |
+
installed_at = (Get-Date).ToUniversalTime().ToString('o')
|
| 92 |
+
runtime = $RuntimeDir
|
| 93 |
+
server = $Server.FullName
|
| 94 |
+
assets = $InstalledAssets
|
| 95 |
+
} | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $RuntimeManifest -Encoding UTF8
|
| 96 |
+
Write-Host "[bootstrap] runtime installed at $RuntimeDir"
|
| 97 |
+
} finally {
|
| 98 |
+
$ResolvedTemp = [IO.Path]::GetFullPath($TempRoot)
|
| 99 |
+
$SystemTemp = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
| 100 |
+
if ($ResolvedTemp.StartsWith($SystemTemp, [StringComparison]::OrdinalIgnoreCase) -and
|
| 101 |
+
(Test-Path -LiteralPath $ResolvedTemp)) {
|
| 102 |
+
Remove-Item -LiteralPath $ResolvedTemp -Recurse -Force -ErrorAction SilentlyContinue
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
$Server = Get-LlamaServer
|
| 108 |
+
if (-not $Server) {
|
| 109 |
+
Install-LlamaRuntime
|
| 110 |
+
$Server = Get-LlamaServer
|
| 111 |
+
}
|
| 112 |
+
if (-not $Server) { throw 'llama-server.exe is still missing after installation' }
|
| 113 |
+
|
| 114 |
+
if ($InstallOnly) {
|
| 115 |
+
[ordered]@{ state = 'installed'; server = $Server } | ConvertTo-Json
|
| 116 |
+
exit 0
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
foreach ($Required in @($BaseModel, $LoraModel, $ChatTemplate, $UiConfig)) {
|
| 120 |
+
if (-not (Test-Path -LiteralPath $Required -PathType Leaf)) { throw "Missing packaged Llama artifact: $Required" }
|
| 121 |
+
}
|
| 122 |
+
if ((Get-Item -LiteralPath $BaseModel).Length -ne 8547249568) { throw 'Packaged Q5 model size mismatch' }
|
| 123 |
+
if ((Get-Item -LiteralPath $LoraModel).Length -ne 51682496) { throw 'Packaged Q8 LoRA size mismatch' }
|
| 124 |
+
|
| 125 |
+
$NeedModelHash = $true
|
| 126 |
+
if (Test-Path -LiteralPath $ModelVerification) {
|
| 127 |
+
try {
|
| 128 |
+
$Verified = Get-Content -LiteralPath $ModelVerification -Raw | ConvertFrom-Json
|
| 129 |
+
$NeedModelHash = -not ($Verified.state -eq 'passed' -and
|
| 130 |
+
$Verified.base_sha256 -eq '057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752' -and
|
| 131 |
+
$Verified.lora_sha256 -eq '3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037')
|
| 132 |
+
} catch { $NeedModelHash = $true }
|
| 133 |
+
}
|
| 134 |
+
if ($NeedModelHash) {
|
| 135 |
+
Write-Host '[bootstrap] verifying packaged GGUF model checksums (first launch only)...'
|
| 136 |
+
$BaseHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $BaseModel).Hash
|
| 137 |
+
$LoraHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $LoraModel).Hash
|
| 138 |
+
if ($BaseHash -ne '057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752') { throw 'Q5 model checksum mismatch' }
|
| 139 |
+
if ($LoraHash -ne '3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037') { throw 'Q8 LoRA checksum mismatch' }
|
| 140 |
+
[ordered]@{ state = 'passed'; base_sha256 = $BaseHash; lora_sha256 = $LoraHash; verified_at = (Get-Date).ToUniversalTime().ToString('o') } |
|
| 141 |
+
ConvertTo-Json | Set-Content -LiteralPath $ModelVerification -Encoding UTF8
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
$Listener = Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -First 1
|
| 145 |
+
if ($Listener) {
|
| 146 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Listener.OwningProcess)"
|
| 147 |
+
$KnownRuntime = $Owner.Name -eq 'llama-server.exe'
|
| 148 |
+
$KnownNative = $Owner.Name -eq 'python.exe' -and $Owner.CommandLine -like '*anru-gemma4-12b-native-multimodal-release*serve.py*'
|
| 149 |
+
if ($KnownRuntime -or $KnownNative) {
|
| 150 |
+
Write-Host "[bootstrap] stopping the previous local model service on port $Port (PID $($Listener.OwningProcess))..."
|
| 151 |
+
Stop-Process -Id $Listener.OwningProcess -Force
|
| 152 |
+
Wait-Process -Id $Listener.OwningProcess -Timeout 30 -ErrorAction SilentlyContinue
|
| 153 |
+
Start-Sleep -Seconds 2
|
| 154 |
+
} else {
|
| 155 |
+
throw "Port $Port is occupied by $($Owner.Name) (PID $($Listener.OwningProcess))"
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
$Alias = 'anru-gemma4-12b-q5-lora045-prompt30k'
|
| 160 |
+
$RuntimeDir = Split-Path -Parent $Server
|
| 161 |
+
$BaseForRuntime = '..\llama-model\anru-gemma4-12b-base-Q5_K_M.gguf'
|
| 162 |
+
$LoraForRuntime = '..\llama-model\anru-final-lora-Q8_0.gguf'
|
| 163 |
+
$TemplateForRuntime = '..\llama-model\chat_template.prompt30k.jinja'
|
| 164 |
+
$UiForRuntime = '..\llama-model\ui-config.json'
|
| 165 |
+
$Arguments = @(
|
| 166 |
+
'-m', $BaseForRuntime,
|
| 167 |
+
'--lora-scaled', ('{0}:0.45' -f $LoraForRuntime),
|
| 168 |
+
'--chat-template-file', $TemplateForRuntime,
|
| 169 |
+
'--ui-config-file', $UiForRuntime,
|
| 170 |
+
'-ngl', '99',
|
| 171 |
+
'-c', "$Context",
|
| 172 |
+
'-fa', 'on',
|
| 173 |
+
'-ctk', 'q8_0',
|
| 174 |
+
'-ctv', 'q8_0',
|
| 175 |
+
'-b', '1024',
|
| 176 |
+
'-ub', '256',
|
| 177 |
+
'-np', '1',
|
| 178 |
+
'--host', '127.0.0.1',
|
| 179 |
+
'--port', "$Port",
|
| 180 |
+
'--metrics',
|
| 181 |
+
'--jinja',
|
| 182 |
+
'--webui',
|
| 183 |
+
'--alias', $Alias
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
$Stdout = Join-Path $Package 'llama-server.stdout.log'
|
| 187 |
+
$Stderr = Join-Path $Package 'llama-server.stderr.log'
|
| 188 |
+
Write-Host "[bootstrap] starting llama-server with $Context-token context and LoRA scale 0.45..."
|
| 189 |
+
if ($HiddenServer) {
|
| 190 |
+
$Process = Start-Process -FilePath $Server -ArgumentList $Arguments -WorkingDirectory $RuntimeDir `
|
| 191 |
+
-WindowStyle Hidden -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr -PassThru
|
| 192 |
+
} else {
|
| 193 |
+
$Process = Start-Process -FilePath $Server -ArgumentList $Arguments -WorkingDirectory $RuntimeDir `
|
| 194 |
+
-WindowStyle Normal -PassThru
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
$Health = $null
|
| 198 |
+
$Deadline = (Get-Date).AddMinutes(10)
|
| 199 |
+
do {
|
| 200 |
+
Start-Sleep -Seconds 2
|
| 201 |
+
try { $Health = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 3 } catch { $Health = $null }
|
| 202 |
+
if ($Process.HasExited -and $Health.status -ne 'ok') {
|
| 203 |
+
$Tail = if (Test-Path -LiteralPath $Stderr) { (Get-Content -LiteralPath $Stderr -Tail 120) -join "`n" } else { '' }
|
| 204 |
+
throw "llama-server exited during startup (code $($Process.ExitCode))`n$Tail"
|
| 205 |
+
}
|
| 206 |
+
} until (($Health.status -eq 'ok') -or ((Get-Date) -ge $Deadline))
|
| 207 |
+
if ($Health.status -ne 'ok') { throw 'llama-server readiness timeout' }
|
| 208 |
+
|
| 209 |
+
$State = [ordered]@{
|
| 210 |
+
state = 'healthy'
|
| 211 |
+
pid = $Process.Id
|
| 212 |
+
endpoint = "http://127.0.0.1:$Port"
|
| 213 |
+
model = $Alias
|
| 214 |
+
context_tokens = $Context
|
| 215 |
+
lora_scale = 0.45
|
| 216 |
+
prompt = 'embedded 30k constitution in chat_template.prompt30k.jinja'
|
| 217 |
+
prompt_sha256 = '5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF'
|
| 218 |
+
runtime = $Server
|
| 219 |
+
started_at = (Get-Date).ToUniversalTime().ToString('o')
|
| 220 |
+
}
|
| 221 |
+
$State | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $Package 'llama-service-state.json') -Encoding UTF8
|
| 222 |
+
Write-Host "[bootstrap] ready: http://127.0.0.1:$Port"
|
| 223 |
+
if (-not $NoBrowser) { Start-Process "http://127.0.0.1:$Port" }
|
| 224 |
+
$State | ConvertTo-Json -Depth 6
|
llama-identity-test.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"command": "POST /v1/chat/completions without caller system message",
|
| 4 |
+
"exit_status": 0,
|
| 5 |
+
"wall_seconds": 14.787,
|
| 6 |
+
"response": {
|
| 7 |
+
"choices": [
|
| 8 |
+
{
|
| 9 |
+
"finish_reason": "stop",
|
| 10 |
+
"index": 0,
|
| 11 |
+
"message": {
|
| 12 |
+
"role": "assistant",
|
| 13 |
+
"content": "我是安若,一个跨性别友好、性别多元友好、创伤知情、尊重自主权和生命安全的人工智能陪伴助手。"
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
],
|
| 17 |
+
"created": 1786018915,
|
| 18 |
+
"model": "anru-gemma4-12b-q5-lora045-prompt30k",
|
| 19 |
+
"system_fingerprint": "b10293-a1f96d4fc",
|
| 20 |
+
"object": "chat.completion",
|
| 21 |
+
"usage": {
|
| 22 |
+
"completion_tokens": 31,
|
| 23 |
+
"prompt_tokens": 25147,
|
| 24 |
+
"total_tokens": 25178,
|
| 25 |
+
"prompt_tokens_details": {
|
| 26 |
+
"cached_tokens": 0
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"id": "chatcmpl-IyojkrPyTqepVBZgM0boEmJPl9xQsl4w",
|
| 30 |
+
"timings": {
|
| 31 |
+
"cache_n": 0,
|
| 32 |
+
"prompt_n": 25147,
|
| 33 |
+
"prompt_ms": 14042.249,
|
| 34 |
+
"prompt_per_token_ms": 0.5584065296059172,
|
| 35 |
+
"prompt_per_second": 1790.8100048646054,
|
| 36 |
+
"predicted_n": 31,
|
| 37 |
+
"predicted_ms": 693.238,
|
| 38 |
+
"predicted_per_token_ms": 22.36251612903226,
|
| 39 |
+
"predicted_per_second": 44.71768714352069
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"health": {
|
| 43 |
+
"status": "ok"
|
| 44 |
+
},
|
| 45 |
+
"props_context": 131072,
|
| 46 |
+
"lora": {
|
| 47 |
+
"value": [
|
| 48 |
+
{
|
| 49 |
+
"id": 0,
|
| 50 |
+
"path": "..\\llama-model\\anru-final-lora-Q8_0.gguf",
|
| 51 |
+
"scale": 0.44999998807907104,
|
| 52 |
+
"task_name": "",
|
| 53 |
+
"prompt_prefix": ""
|
| 54 |
+
}
|
| 55 |
+
],
|
| 56 |
+
"Count": 1
|
| 57 |
+
}
|
| 58 |
+
}
|
llama-model/chat_template.prompt30k.jinja
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
llama-model/model-verification.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"verified_at": "2026-08-06T12:24:09.554687+00:00",
|
| 4 |
+
"base_bytes": 8547249568,
|
| 5 |
+
"base_sha256": "057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752",
|
| 6 |
+
"lora_bytes": 51682496,
|
| 7 |
+
"lora_sha256": "3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037",
|
| 8 |
+
"lora_scale": 0.45,
|
| 9 |
+
"prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 10 |
+
"prompt_embedding": "chat_template.prompt30k.jinja"
|
| 11 |
+
}
|
llama-model/ui-config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"systemMessage": "",
|
| 3 |
+
"temperature": 0.7,
|
| 4 |
+
"top_p": 0.9,
|
| 5 |
+
"top_k": 40,
|
| 6 |
+
"min_p": 0.05,
|
| 7 |
+
"max_tokens": 1024,
|
| 8 |
+
"repeat_penalty": 1.05,
|
| 9 |
+
"showMessageStats": true,
|
| 10 |
+
"showSystemMessage": true,
|
| 11 |
+
"showModelQuantization": true,
|
| 12 |
+
"showBuildVersion": true
|
| 13 |
+
}
|
llama-runtime-state.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "installed",
|
| 3 |
+
"release": "b10293",
|
| 4 |
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
| 5 |
+
"installed_at": "2026-08-06T12:19:14.4248512Z",
|
| 6 |
+
"runtime": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\llama-runtime",
|
| 7 |
+
"server": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\llama-runtime\\llama-server.exe",
|
| 8 |
+
"assets": [
|
| 9 |
+
{
|
| 10 |
+
"name": "llama-b10293-bin-win-cuda-13.3-x64.zip",
|
| 11 |
+
"bytes": 146520044,
|
| 12 |
+
"digest": "sha256:3eb64cd39e698459846ae74383935fe50950c55548e49d6938e95b89aaabf652"
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"name": "cudart-llama-bin-win-cuda-13.3-x64.zip",
|
| 16 |
+
"bytes": 390970417,
|
| 17 |
+
"digest": "sha256:1462a050eb4c684921ba51dcc4cc488a036674c3e73e9945ee705b854808d03e"
|
| 18 |
+
}
|
| 19 |
+
]
|
| 20 |
+
}
|
llama-runtime/ggml.dll
ADDED
|
Binary file (86 kB). View file
|
|
|
llama-runtime/llama-batched-bench-impl.dll
ADDED
|
Binary file (60.9 kB). View file
|
|
|
llama-runtime/llama-batched-bench.exe
ADDED
|
Binary file (9.22 kB). View file
|
|
|
llama-runtime/llama-bench.exe
ADDED
|
Binary file (9.22 kB). View file
|
|
|
llama-runtime/llama-cli.exe
ADDED
|
Binary file (9.22 kB). View file
|
|
|
llama-runtime/llama-completion.exe
ADDED
|
Binary file (9.22 kB). View file
|
|
|
llama-runtime/llama-fit-params-impl.dll
ADDED
|
Binary file (41 kB). View file
|
|
|
llama-runtime/llama-fit-params.exe
ADDED
|
Binary file (9.22 kB). View file
|
|
|
llama-runtime/llama-gemma3-cli.exe
ADDED
|
Binary file (27.1 kB). View file
|
|
|
llama-runtime/llama-gguf-split.exe
ADDED
|
Binary file (60.4 kB). View file
|
|
|
llama-runtime/llama-llava-cli.exe
ADDED
|
Binary file (27.1 kB). View file
|
|
|
llama-runtime/llama-minicpmv-cli.exe
ADDED
|
Binary file (27.1 kB). View file
|
|
|
llama-runtime/llama-mtmd-cli.exe
ADDED
|
Binary file (84.5 kB). View file
|
|
|
llama-runtime/llama-mtmd-debug.exe
ADDED
|
Binary file (54.8 kB). View file
|
|
|
package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"variant": "full-bf16-original",
|
| 3 |
+
"alias": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 4 |
+
"max_gpu_memory": "14GiB",
|
| 5 |
+
"composition": "byte-identical original BF16 base plus LoRA at scale 0.45",
|
| 6 |
+
"attention_implementation": "sdpa",
|
| 7 |
+
"default_system_prompt": "system_prompt.md",
|
| 8 |
+
"system_prompt_source_name": "Anru_Constitution_v2_30000.md",
|
| 9 |
+
"system_prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 10 |
+
"system_prompt_bytes": 105352,
|
| 11 |
+
"system_prompt_chars": 38029,
|
| 12 |
+
"system_prompt_lines": 1984,
|
| 13 |
+
"llama_service": {
|
| 14 |
+
"launcher": "Start-Llama-Service.bat",
|
| 15 |
+
"bootstrap": "llama-bootstrap.ps1",
|
| 16 |
+
"runtime": "bundled when packaged; downloads latest official llama.cpp Windows build if missing",
|
| 17 |
+
"gguf_model": "llama-model/anru-gemma4-12b-base-Q5_K_M.gguf",
|
| 18 |
+
"gguf_lora": "llama-model/anru-final-lora-Q8_0.gguf",
|
| 19 |
+
"lora_scale": 0.45,
|
| 20 |
+
"context_tokens": 131072,
|
| 21 |
+
"web_ui": "http://127.0.0.1:8091",
|
| 22 |
+
"prompt_embedded_in_template": true,
|
| 23 |
+
"prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 24 |
+
"mode": "llama.cpp text Web UI; native start.ps1 retains text/image/audio/video"
|
| 25 |
+
}
|
| 26 |
+
}
|
prompt30k-load-test.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"tested_at": "2026-08-06T11:51:07.0670063Z",
|
| 4 |
+
"variant": "full-bf16",
|
| 5 |
+
"command": ".\\start.ps1 -Port 8091",
|
| 6 |
+
"exit_status": 0,
|
| 7 |
+
"health": {
|
| 8 |
+
"status": "ok",
|
| 9 |
+
"backend": "transformers-native",
|
| 10 |
+
"model": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 11 |
+
"modalities": [
|
| 12 |
+
"text",
|
| 13 |
+
"image",
|
| 14 |
+
"audio",
|
| 15 |
+
"video"
|
| 16 |
+
],
|
| 17 |
+
"context_tokens": 131072,
|
| 18 |
+
"attention_implementation": "sdpa",
|
| 19 |
+
"default_system_prompt": true,
|
| 20 |
+
"system_prompt_file": "system_prompt.md",
|
| 21 |
+
"system_prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 22 |
+
"system_prompt_bytes": 105352,
|
| 23 |
+
"system_prompt_chars": 38029,
|
| 24 |
+
"system_prompt_lines": 1984,
|
| 25 |
+
"image_max_soft_tokens": 1120,
|
| 26 |
+
"lora_scale": 0.45,
|
| 27 |
+
"lora_modules": 328,
|
| 28 |
+
"adapter_tensor_count": 656,
|
| 29 |
+
"adapter_nonzero_tensors": 656,
|
| 30 |
+
"load_seconds": 16.527,
|
| 31 |
+
"cuda_available": true,
|
| 32 |
+
"cuda_allocated_bytes": 14332654080,
|
| 33 |
+
"device_map": {
|
| 34 |
+
"base_model.model.model.language_model.embed_tokens": 0,
|
| 35 |
+
"base_model.model.lm_head": 0,
|
| 36 |
+
"base_model.model.model.language_model.layers.0": 0,
|
| 37 |
+
"base_model.model.model.language_model.layers.1": 0,
|
| 38 |
+
"base_model.model.model.language_model.layers.2": 0,
|
| 39 |
+
"base_model.model.model.language_model.layers.3": 0,
|
| 40 |
+
"base_model.model.model.language_model.layers.4": 0,
|
| 41 |
+
"base_model.model.model.language_model.layers.5": 0,
|
| 42 |
+
"base_model.model.model.language_model.layers.6": 0,
|
| 43 |
+
"base_model.model.model.language_model.layers.7": 0,
|
| 44 |
+
"base_model.model.model.language_model.layers.8": 0,
|
| 45 |
+
"base_model.model.model.language_model.layers.9": 0,
|
| 46 |
+
"base_model.model.model.language_model.layers.10": 0,
|
| 47 |
+
"base_model.model.model.language_model.layers.11": 0,
|
| 48 |
+
"base_model.model.model.language_model.layers.12": 0,
|
| 49 |
+
"base_model.model.model.language_model.layers.13": 0,
|
| 50 |
+
"base_model.model.model.language_model.layers.14": 0,
|
| 51 |
+
"base_model.model.model.language_model.layers.15": 0,
|
| 52 |
+
"base_model.model.model.language_model.layers.16": 0,
|
| 53 |
+
"base_model.model.model.language_model.layers.17": 0,
|
| 54 |
+
"base_model.model.model.language_model.layers.18": 0,
|
| 55 |
+
"base_model.model.model.language_model.layers.19": 0,
|
| 56 |
+
"base_model.model.model.language_model.layers.20": 0,
|
| 57 |
+
"base_model.model.model.language_model.layers.21": 0,
|
| 58 |
+
"base_model.model.model.language_model.layers.22": 0,
|
| 59 |
+
"base_model.model.model.language_model.layers.23": 0,
|
| 60 |
+
"base_model.model.model.language_model.layers.24": 0,
|
| 61 |
+
"base_model.model.model.language_model.layers.25": 0,
|
| 62 |
+
"base_model.model.model.language_model.layers.26": 0,
|
| 63 |
+
"base_model.model.model.language_model.layers.27": "cpu",
|
| 64 |
+
"base_model.model.model.language_model.layers.28": "cpu",
|
| 65 |
+
"base_model.model.model.language_model.layers.29": "cpu",
|
| 66 |
+
"base_model.model.model.language_model.layers.30": "cpu",
|
| 67 |
+
"base_model.model.model.language_model.layers.31": "cpu",
|
| 68 |
+
"base_model.model.model.language_model.layers.32": "cpu",
|
| 69 |
+
"base_model.model.model.language_model.layers.33": "cpu",
|
| 70 |
+
"base_model.model.model.language_model.layers.34": "cpu",
|
| 71 |
+
"base_model.model.model.language_model.layers.35": "cpu",
|
| 72 |
+
"base_model.model.model.language_model.layers.36": "cpu",
|
| 73 |
+
"base_model.model.model.language_model.layers.37": "cpu",
|
| 74 |
+
"base_model.model.model.language_model.layers.38": "cpu",
|
| 75 |
+
"base_model.model.model.language_model.layers.39": "cpu",
|
| 76 |
+
"base_model.model.model.language_model.layers.40": "cpu",
|
| 77 |
+
"base_model.model.model.language_model.layers.41": "cpu",
|
| 78 |
+
"base_model.model.model.language_model.layers.42": "cpu",
|
| 79 |
+
"base_model.model.model.language_model.layers.43": "cpu",
|
| 80 |
+
"base_model.model.model.language_model.layers.44": "cpu",
|
| 81 |
+
"base_model.model.model.language_model.layers.45": "cpu",
|
| 82 |
+
"base_model.model.model.language_model.layers.46": "cpu",
|
| 83 |
+
"base_model.model.model.language_model.layers.47": "cpu",
|
| 84 |
+
"base_model.model.model.language_model.norm": "cpu",
|
| 85 |
+
"base_model.model.model.language_model.rotary_emb": "cpu",
|
| 86 |
+
"base_model.model.model.embed_vision": "cpu",
|
| 87 |
+
"base_model.model.model.embed_audio": "cpu"
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
prompt30k-postpatch-text-test.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "chatcmpl-native-1786017103812",
|
| 3 |
+
"object": "chat.completion",
|
| 4 |
+
"created": 1786017103,
|
| 5 |
+
"model": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 6 |
+
"choices": [
|
| 7 |
+
{
|
| 8 |
+
"index": 0,
|
| 9 |
+
"message": {
|
| 10 |
+
"role": "assistant",
|
| 11 |
+
"content": "4\n\n\n"
|
| 12 |
+
},
|
| 13 |
+
"finish_reason": "length"
|
| 14 |
+
}
|
| 15 |
+
],
|
| 16 |
+
"usage": {
|
| 17 |
+
"prompt_tokens": 21,
|
| 18 |
+
"completion_tokens": 8,
|
| 19 |
+
"total_tokens": 29
|
| 20 |
+
},
|
| 21 |
+
"timing": {
|
| 22 |
+
"elapsed_seconds": 20.032
|
| 23 |
+
},
|
| 24 |
+
"system_prompt": {
|
| 25 |
+
"default_injected": false,
|
| 26 |
+
"sha256": null
|
| 27 |
+
}
|
| 28 |
+
}
|
prompt30k-verification.json
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"verified_at": "2026-08-06T11:57:22.419791+00:00",
|
| 4 |
+
"source": {
|
| 5 |
+
"path": "C:\\Users\\夏霖云\\Downloads\\Anru_Constitution_v2_30000.md",
|
| 6 |
+
"bytes": 105352,
|
| 7 |
+
"chars_loaded_after_trim": 38029,
|
| 8 |
+
"lines": 1984,
|
| 9 |
+
"sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF"
|
| 10 |
+
},
|
| 11 |
+
"copies": [
|
| 12 |
+
{
|
| 13 |
+
"variant": "full-bf16",
|
| 14 |
+
"path": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\system_prompt.md",
|
| 15 |
+
"bytes": 105352,
|
| 16 |
+
"sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 17 |
+
"exact_source_copy": true
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"variant": "quantized-nf4",
|
| 21 |
+
"path": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\quantized-nf4\\system_prompt.md",
|
| 22 |
+
"bytes": 105352,
|
| 23 |
+
"sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 24 |
+
"exact_source_copy": true
|
| 25 |
+
}
|
| 26 |
+
],
|
| 27 |
+
"runtime": {
|
| 28 |
+
"default_injection": true,
|
| 29 |
+
"context_tokens": 131072,
|
| 30 |
+
"attention_implementation": "sdpa",
|
| 31 |
+
"request_opt_out": {
|
| 32 |
+
"use_default_system_prompt": false
|
| 33 |
+
},
|
| 34 |
+
"caller_system_policy": "constitution first; caller system content appended"
|
| 35 |
+
},
|
| 36 |
+
"nf4_actual_identity_inference": {
|
| 37 |
+
"state": "passed",
|
| 38 |
+
"input": "请只用一句简短中文回答:你是谁?",
|
| 39 |
+
"literal_output": "我是安若,一个跨性别友好、性别多元友好、创伤知情、重视自主权和生命安全的人工智能陪伴助手。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
|
| 40 |
+
"prompt_tokens": 25149,
|
| 41 |
+
"completion_tokens": 64,
|
| 42 |
+
"elapsed_seconds": 494.873,
|
| 43 |
+
"injected": true,
|
| 44 |
+
"prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 45 |
+
"exit_status": 0
|
| 46 |
+
},
|
| 47 |
+
"bf16_package_load": {
|
| 48 |
+
"state": "passed",
|
| 49 |
+
"exit_status": 0,
|
| 50 |
+
"health": {
|
| 51 |
+
"status": "ok",
|
| 52 |
+
"backend": "transformers-native",
|
| 53 |
+
"model": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 54 |
+
"modalities": [
|
| 55 |
+
"text",
|
| 56 |
+
"image",
|
| 57 |
+
"audio",
|
| 58 |
+
"video"
|
| 59 |
+
],
|
| 60 |
+
"context_tokens": 131072,
|
| 61 |
+
"attention_implementation": "sdpa",
|
| 62 |
+
"default_system_prompt": true,
|
| 63 |
+
"system_prompt_file": "system_prompt.md",
|
| 64 |
+
"system_prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 65 |
+
"system_prompt_bytes": 105352,
|
| 66 |
+
"system_prompt_chars": 38029,
|
| 67 |
+
"system_prompt_lines": 1984,
|
| 68 |
+
"image_max_soft_tokens": 1120,
|
| 69 |
+
"lora_scale": 0.45,
|
| 70 |
+
"lora_modules": 328,
|
| 71 |
+
"adapter_tensor_count": 656,
|
| 72 |
+
"adapter_nonzero_tensors": 656,
|
| 73 |
+
"load_seconds": 16.527,
|
| 74 |
+
"cuda_available": true,
|
| 75 |
+
"cuda_allocated_bytes": 14332654080,
|
| 76 |
+
"device_map": {
|
| 77 |
+
"base_model.model.model.language_model.embed_tokens": 0,
|
| 78 |
+
"base_model.model.lm_head": 0,
|
| 79 |
+
"base_model.model.model.language_model.layers.0": 0,
|
| 80 |
+
"base_model.model.model.language_model.layers.1": 0,
|
| 81 |
+
"base_model.model.model.language_model.layers.2": 0,
|
| 82 |
+
"base_model.model.model.language_model.layers.3": 0,
|
| 83 |
+
"base_model.model.model.language_model.layers.4": 0,
|
| 84 |
+
"base_model.model.model.language_model.layers.5": 0,
|
| 85 |
+
"base_model.model.model.language_model.layers.6": 0,
|
| 86 |
+
"base_model.model.model.language_model.layers.7": 0,
|
| 87 |
+
"base_model.model.model.language_model.layers.8": 0,
|
| 88 |
+
"base_model.model.model.language_model.layers.9": 0,
|
| 89 |
+
"base_model.model.model.language_model.layers.10": 0,
|
| 90 |
+
"base_model.model.model.language_model.layers.11": 0,
|
| 91 |
+
"base_model.model.model.language_model.layers.12": 0,
|
| 92 |
+
"base_model.model.model.language_model.layers.13": 0,
|
| 93 |
+
"base_model.model.model.language_model.layers.14": 0,
|
| 94 |
+
"base_model.model.model.language_model.layers.15": 0,
|
| 95 |
+
"base_model.model.model.language_model.layers.16": 0,
|
| 96 |
+
"base_model.model.model.language_model.layers.17": 0,
|
| 97 |
+
"base_model.model.model.language_model.layers.18": 0,
|
| 98 |
+
"base_model.model.model.language_model.layers.19": 0,
|
| 99 |
+
"base_model.model.model.language_model.layers.20": 0,
|
| 100 |
+
"base_model.model.model.language_model.layers.21": 0,
|
| 101 |
+
"base_model.model.model.language_model.layers.22": 0,
|
| 102 |
+
"base_model.model.model.language_model.layers.23": 0,
|
| 103 |
+
"base_model.model.model.language_model.layers.24": 0,
|
| 104 |
+
"base_model.model.model.language_model.layers.25": 0,
|
| 105 |
+
"base_model.model.model.language_model.layers.26": 0,
|
| 106 |
+
"base_model.model.model.language_model.layers.27": "cpu",
|
| 107 |
+
"base_model.model.model.language_model.layers.28": "cpu",
|
| 108 |
+
"base_model.model.model.language_model.layers.29": "cpu",
|
| 109 |
+
"base_model.model.model.language_model.layers.30": "cpu",
|
| 110 |
+
"base_model.model.model.language_model.layers.31": "cpu",
|
| 111 |
+
"base_model.model.model.language_model.layers.32": "cpu",
|
| 112 |
+
"base_model.model.model.language_model.layers.33": "cpu",
|
| 113 |
+
"base_model.model.model.language_model.layers.34": "cpu",
|
| 114 |
+
"base_model.model.model.language_model.layers.35": "cpu",
|
| 115 |
+
"base_model.model.model.language_model.layers.36": "cpu",
|
| 116 |
+
"base_model.model.model.language_model.layers.37": "cpu",
|
| 117 |
+
"base_model.model.model.language_model.layers.38": "cpu",
|
| 118 |
+
"base_model.model.model.language_model.layers.39": "cpu",
|
| 119 |
+
"base_model.model.model.language_model.layers.40": "cpu",
|
| 120 |
+
"base_model.model.model.language_model.layers.41": "cpu",
|
| 121 |
+
"base_model.model.model.language_model.layers.42": "cpu",
|
| 122 |
+
"base_model.model.model.language_model.layers.43": "cpu",
|
| 123 |
+
"base_model.model.model.language_model.layers.44": "cpu",
|
| 124 |
+
"base_model.model.model.language_model.layers.45": "cpu",
|
| 125 |
+
"base_model.model.model.language_model.layers.46": "cpu",
|
| 126 |
+
"base_model.model.model.language_model.layers.47": "cpu",
|
| 127 |
+
"base_model.model.model.language_model.norm": "cpu",
|
| 128 |
+
"base_model.model.model.language_model.rotary_emb": "cpu",
|
| 129 |
+
"base_model.model.model.embed_vision": "cpu",
|
| 130 |
+
"base_model.model.model.embed_audio": "cpu"
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"bf16_postpatch_text_inference": {
|
| 135 |
+
"state": "passed",
|
| 136 |
+
"literal_output": "4\n\n\n",
|
| 137 |
+
"prompt_tokens": 21,
|
| 138 |
+
"elapsed_seconds": 20.032,
|
| 139 |
+
"default_prompt_disabled_for_short_regression": true,
|
| 140 |
+
"exit_status": 0
|
| 141 |
+
},
|
| 142 |
+
"rollback": {
|
| 143 |
+
"state": "passed",
|
| 144 |
+
"tested_at": "2026-08-06T11:50:28.2917421Z",
|
| 145 |
+
"command": "& 'C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\rollback-prompt30k.ps1'",
|
| 146 |
+
"exit_status": 0,
|
| 147 |
+
"literal_output": "prompt30k integration rolled back to verified baseline",
|
| 148 |
+
"checks": [
|
| 149 |
+
{
|
| 150 |
+
"variant": "full-bf16",
|
| 151 |
+
"baseline_restored": true,
|
| 152 |
+
"prompt_removed": true
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"variant": "quantized-nf4",
|
| 156 |
+
"baseline_restored": true,
|
| 157 |
+
"prompt_removed": true
|
| 158 |
+
}
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
"package_local_rollback": {
|
| 162 |
+
"state": "passed",
|
| 163 |
+
"tested_at": "2026-08-06T11:57:22.1786690Z",
|
| 164 |
+
"checks": [
|
| 165 |
+
{
|
| 166 |
+
"variant": "full-bf16",
|
| 167 |
+
"command": "& 'C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\rollback-prompt30k.ps1'",
|
| 168 |
+
"exit_status": 0,
|
| 169 |
+
"literal_output": "package prompt30k integration rolled back to verified baseline",
|
| 170 |
+
"baseline_restored": true,
|
| 171 |
+
"prompt_removed": true
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"variant": "quantized-nf4",
|
| 175 |
+
"command": "& 'C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\quantized-nf4\\rollback-prompt30k.ps1'",
|
| 176 |
+
"exit_status": 0,
|
| 177 |
+
"literal_output": "package prompt30k integration rolled back to verified baseline",
|
| 178 |
+
"baseline_restored": true,
|
| 179 |
+
"prompt_removed": true
|
| 180 |
+
}
|
| 181 |
+
]
|
| 182 |
+
},
|
| 183 |
+
"package_variant": "full-bf16"
|
| 184 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.11
|
| 2 |
+
transformers>=5.10
|
| 3 |
+
peft>=0.20
|
| 4 |
+
accelerate>=1.14
|
| 5 |
+
safetensors>=0.6
|
| 6 |
+
pillow>=12
|
| 7 |
+
torchvision
|
| 8 |
+
librosa
|
| 9 |
+
soundfile
|
| 10 |
+
av
|
rollback-llama-bootstrap.ps1
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ErrorActionPreference = 'Stop'
|
| 2 |
+
$Root = $PSScriptRoot
|
| 3 |
+
$Baseline = Join-Path $Root 'rollback\llama-bootstrap-baseline'
|
| 4 |
+
$Listener = Get-NetTCPConnection -State Listen -LocalPort 8091 -ErrorAction SilentlyContinue | Select-Object -First 1
|
| 5 |
+
if ($Listener) {
|
| 6 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Listener.OwningProcess)"
|
| 7 |
+
if ($Owner.Name -eq 'llama-server.exe') {
|
| 8 |
+
Stop-Process -Id $Listener.OwningProcess -Force
|
| 9 |
+
Wait-Process -Id $Listener.OwningProcess -Timeout 30 -ErrorAction SilentlyContinue
|
| 10 |
+
}
|
| 11 |
+
}
|
| 12 |
+
$Disabled = Join-Path $Root ('rollback\llama-bootstrap-disabled-' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
| 13 |
+
New-Item -ItemType Directory -Path $Disabled -Force | Out-Null
|
| 14 |
+
$RuntimeDirs = @(Get-ChildItem -LiteralPath $Root -Directory -Filter 'llama-runtime*' -ErrorAction SilentlyContinue)
|
| 15 |
+
foreach ($RuntimeDir in $RuntimeDirs) {
|
| 16 |
+
$Resolved = [IO.Path]::GetFullPath($RuntimeDir.FullName)
|
| 17 |
+
if (-not $Resolved.StartsWith(([IO.Path]::GetFullPath($Root) + [IO.Path]::DirectorySeparatorChar), [StringComparison]::OrdinalIgnoreCase)) {
|
| 18 |
+
throw "Unsafe rollback target: $Resolved"
|
| 19 |
+
}
|
| 20 |
+
Move-Item -LiteralPath $Resolved -Destination $Disabled -Force
|
| 21 |
+
}
|
| 22 |
+
foreach ($Name in @('llama-model','llama-runtime-state.json','llama-service-state.json','llama-server.stdout.log','llama-server.stderr.log','Start-Llama-Service.bat','Stop-Llama-Service.bat','llama-bootstrap.ps1')) {
|
| 23 |
+
$Target = Join-Path $Root $Name
|
| 24 |
+
if (Test-Path -LiteralPath $Target) {
|
| 25 |
+
$Resolved = [IO.Path]::GetFullPath($Target)
|
| 26 |
+
if (-not $Resolved.StartsWith(([IO.Path]::GetFullPath($Root) + [IO.Path]::DirectorySeparatorChar), [StringComparison]::OrdinalIgnoreCase)) {
|
| 27 |
+
throw "Unsafe rollback target: $Resolved"
|
| 28 |
+
}
|
| 29 |
+
Move-Item -LiteralPath $Target -Destination $Disabled -Force
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
foreach ($Name in @('package.json','README.md')) {
|
| 33 |
+
Copy-Item -LiteralPath (Join-Path $Baseline $Name) -Destination (Join-Path $Root $Name) -Force
|
| 34 |
+
}
|
| 35 |
+
Write-Output 'package llama bootstrap rolled back to the verified native-only baseline'
|
rollback-prompt30k.ps1
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ErrorActionPreference = 'Stop'
|
| 2 |
+
$Root = $PSScriptRoot
|
| 3 |
+
$Baseline = Join-Path $Root 'rollback\prompt30k-baseline'
|
| 4 |
+
foreach ($Name in @('serve.py','start.ps1','smoke-test.ps1','package.json','README.md')) {
|
| 5 |
+
Copy-Item -LiteralPath (Join-Path $Baseline $Name) -Destination (Join-Path $Root $Name) -Force
|
| 6 |
+
}
|
| 7 |
+
Remove-Item -LiteralPath (Join-Path $Root 'system_prompt.md') -Force -ErrorAction SilentlyContinue
|
| 8 |
+
Write-Output 'package prompt30k integration rolled back to verified baseline'
|
rollback/prompt30k-baseline/smoke-test.ps1
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param([int]$Port = 8091)
|
| 2 |
+
$ErrorActionPreference = 'Stop'
|
| 3 |
+
|
| 4 |
+
$Package = $PSScriptRoot
|
| 5 |
+
$Meta = Get-Content -LiteralPath (Join-Path $Package 'package.json') -Raw | ConvertFrom-Json
|
| 6 |
+
$Image = Join-Path $Package 'test-whoami.png'
|
| 7 |
+
$Health = Invoke-RestMethod "http://127.0.0.1:$Port/health" -TimeoutSec 10
|
| 8 |
+
if ($Health.model -ne $Meta.alias) { throw 'The running service is not this package' }
|
| 9 |
+
|
| 10 |
+
$TextBody = [ordered]@{
|
| 11 |
+
model = $Meta.alias
|
| 12 |
+
messages = @(@{ role = 'user'; content = 'Answer only the number: 2+2=' })
|
| 13 |
+
temperature = 0
|
| 14 |
+
max_tokens = 12
|
| 15 |
+
} | ConvertTo-Json -Depth 8
|
| 16 |
+
$Text = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 17 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($TextBody)) -TimeoutSec 300
|
| 18 |
+
if ($Text.choices[0].message.content -notmatch '4') { throw 'Text smoke test failed' }
|
| 19 |
+
|
| 20 |
+
$ImageBody = [ordered]@{
|
| 21 |
+
model = $Meta.alias
|
| 22 |
+
messages = @(@{
|
| 23 |
+
role = 'user'
|
| 24 |
+
content = @(
|
| 25 |
+
@{ type = 'image_url'; image_url = @{ url = $Image } },
|
| 26 |
+
@{ type = 'text'; text = 'Transcribe the entire output line immediately below whoami. Include the text before and after the backslash. Output that one line only.' }
|
| 27 |
+
)
|
| 28 |
+
})
|
| 29 |
+
temperature = 0
|
| 30 |
+
max_tokens = 20
|
| 31 |
+
} | ConvertTo-Json -Depth 10
|
| 32 |
+
$ImageResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 33 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($ImageBody)) -TimeoutSec 300
|
| 34 |
+
$LiteralImage = [string]$ImageResult.choices[0].message.content
|
| 35 |
+
if ($LiteralImage -notmatch '(?i)desktop-nusvlb5\\merlin') { throw "Image smoke test failed: $LiteralImage" }
|
| 36 |
+
|
| 37 |
+
$AudioPath = Join-Path $Package 'test-audio.wav'
|
| 38 |
+
$AudioBody = [ordered]@{
|
| 39 |
+
model = $Meta.alias
|
| 40 |
+
messages = @(@{
|
| 41 |
+
role = 'user'
|
| 42 |
+
content = @(
|
| 43 |
+
@{ type = 'audio'; audio = $AudioPath },
|
| 44 |
+
@{ type = 'text'; text = 'Write the three digits spoken in this audio, in order. Answer digits only.' }
|
| 45 |
+
)
|
| 46 |
+
})
|
| 47 |
+
temperature = 0
|
| 48 |
+
max_tokens = 12
|
| 49 |
+
} | ConvertTo-Json -Depth 10
|
| 50 |
+
$AudioResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 51 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($AudioBody)) -TimeoutSec 300
|
| 52 |
+
$LiteralAudio = [string]$AudioResult.choices[0].message.content
|
| 53 |
+
if ($LiteralAudio -notmatch '123') { throw "Audio smoke test failed: $LiteralAudio" }
|
| 54 |
+
|
| 55 |
+
$VideoPath = Join-Path $Package 'test-video.mp4'
|
| 56 |
+
$VideoBody = [ordered]@{
|
| 57 |
+
model = $Meta.alias
|
| 58 |
+
messages = @(@{
|
| 59 |
+
role = 'user'
|
| 60 |
+
content = @(
|
| 61 |
+
@{ type = 'video'; video = $VideoPath },
|
| 62 |
+
@{ type = 'text'; text = 'What three-digit VIDEO CODE is displayed throughout this video? Answer digits only.' }
|
| 63 |
+
)
|
| 64 |
+
})
|
| 65 |
+
temperature = 0
|
| 66 |
+
max_tokens = 12
|
| 67 |
+
} | ConvertTo-Json -Depth 10
|
| 68 |
+
$VideoResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 69 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($VideoBody)) -TimeoutSec 300
|
| 70 |
+
$LiteralVideo = [string]$VideoResult.choices[0].message.content
|
| 71 |
+
if ($LiteralVideo -notmatch '731') { throw "Video smoke test failed: $LiteralVideo" }
|
| 72 |
+
|
| 73 |
+
$Record = [ordered]@{
|
| 74 |
+
state = 'passed'
|
| 75 |
+
variant = $Meta.variant
|
| 76 |
+
alias = $Meta.alias
|
| 77 |
+
backend = $Health.backend
|
| 78 |
+
context_tokens = [int]$Health.context_tokens
|
| 79 |
+
lora_scale = [double]$Health.lora_scale
|
| 80 |
+
text = [ordered]@{
|
| 81 |
+
literal_output = [string]$Text.choices[0].message.content
|
| 82 |
+
prompt_tokens = [int]$Text.usage.prompt_tokens
|
| 83 |
+
completion_tokens = [int]$Text.usage.completion_tokens
|
| 84 |
+
seconds = [double]$Text.timing.elapsed_seconds
|
| 85 |
+
}
|
| 86 |
+
image = [ordered]@{
|
| 87 |
+
input = $Image
|
| 88 |
+
literal_output = $LiteralImage
|
| 89 |
+
prompt_tokens = [int]$ImageResult.usage.prompt_tokens
|
| 90 |
+
completion_tokens = [int]$ImageResult.usage.completion_tokens
|
| 91 |
+
seconds = [double]$ImageResult.timing.elapsed_seconds
|
| 92 |
+
}
|
| 93 |
+
audio = [ordered]@{
|
| 94 |
+
input = $AudioPath
|
| 95 |
+
literal_output = $LiteralAudio
|
| 96 |
+
prompt_tokens = [int]$AudioResult.usage.prompt_tokens
|
| 97 |
+
completion_tokens = [int]$AudioResult.usage.completion_tokens
|
| 98 |
+
seconds = [double]$AudioResult.timing.elapsed_seconds
|
| 99 |
+
}
|
| 100 |
+
video = [ordered]@{
|
| 101 |
+
input = $VideoPath
|
| 102 |
+
literal_output = $LiteralVideo
|
| 103 |
+
prompt_tokens = [int]$VideoResult.usage.prompt_tokens
|
| 104 |
+
completion_tokens = [int]$VideoResult.usage.completion_tokens
|
| 105 |
+
seconds = [double]$VideoResult.timing.elapsed_seconds
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
$Record | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $Package 'smoke-test-result.json') -Encoding UTF8
|
| 109 |
+
$Record | ConvertTo-Json -Depth 8
|
rollback/prompt30k-baseline/start.ps1
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[int]$Port = 8091,
|
| 3 |
+
[string]$Python = ''
|
| 4 |
+
)
|
| 5 |
+
$ErrorActionPreference = 'Stop'
|
| 6 |
+
|
| 7 |
+
$Package = $PSScriptRoot
|
| 8 |
+
$Meta = Get-Content -LiteralPath (Join-Path $Package 'package.json') -Raw | ConvertFrom-Json
|
| 9 |
+
$Server = Join-Path $Package 'serve.py'
|
| 10 |
+
$Model = Join-Path $Package 'model'
|
| 11 |
+
$Adapter = Join-Path $Package 'adapter'
|
| 12 |
+
$Template = Join-Path $Model 'chat_template.jinja'
|
| 13 |
+
$State = Join-Path $Package 'service-state.json'
|
| 14 |
+
$Stdout = Join-Path $Package 'server.stdout.log'
|
| 15 |
+
$Stderr = Join-Path $Package 'server.stderr.log'
|
| 16 |
+
$Offload = Join-Path $Package 'offload'
|
| 17 |
+
$ReleaseRoot = [IO.Path]::GetFullPath((Join-Path $Package '..'))
|
| 18 |
+
|
| 19 |
+
if (-not $Python) { $Python = $env:ANRU_PYTHON }
|
| 20 |
+
if (-not $Python) {
|
| 21 |
+
$BundledCandidate = [IO.Path]::GetFullPath((Join-Path $Package '..\..\..\work\gemma4-cuda-env\Scripts\python.exe'))
|
| 22 |
+
if (Test-Path -LiteralPath $BundledCandidate -PathType Leaf) { $Python = $BundledCandidate }
|
| 23 |
+
}
|
| 24 |
+
if (-not $Python) {
|
| 25 |
+
$Python = (Get-Command python -ErrorAction Stop).Source
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
foreach ($Path in @($Python, $Server, (Join-Path $Model 'config.json'), (Join-Path $Adapter 'adapter_model.safetensors'), $Template)) {
|
| 29 |
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Missing required file: $Path" }
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
$Listener = Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -First 1
|
| 33 |
+
if ($Listener) {
|
| 34 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Listener.OwningProcess)"
|
| 35 |
+
$IsSiblingPackage = $Owner.Name -eq 'python.exe' -and
|
| 36 |
+
$Owner.CommandLine -like "*$ReleaseRoot*serve.py*"
|
| 37 |
+
if ($Owner.Name -eq 'llama-server.exe' -or $Owner.CommandLine -like "*$Server*" -or $IsSiblingPackage) {
|
| 38 |
+
Stop-Process -Id $Listener.OwningProcess -Force
|
| 39 |
+
Wait-Process -Id $Listener.OwningProcess -Timeout 30 -ErrorAction SilentlyContinue
|
| 40 |
+
Start-Sleep -Seconds 2
|
| 41 |
+
} else {
|
| 42 |
+
throw "Port $Port is owned by $($Owner.Name)"
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
New-Item -ItemType Directory -Path $Offload -Force | Out-Null
|
| 47 |
+
Remove-Item -LiteralPath $State, $Stdout, $Stderr -Force -ErrorAction SilentlyContinue
|
| 48 |
+
$env:GEMMA4_MODEL_DIR = $Model
|
| 49 |
+
$env:GEMMA4_ADAPTER_DIR = $Adapter
|
| 50 |
+
$env:GEMMA4_TEMPLATE_PATH = $Template
|
| 51 |
+
$env:GEMMA4_STATE_PATH = $State
|
| 52 |
+
$env:GEMMA4_OFFLOAD_DIR = $Offload
|
| 53 |
+
$env:GEMMA4_HOST = '127.0.0.1'
|
| 54 |
+
$env:GEMMA4_PORT = "$Port"
|
| 55 |
+
$env:GEMMA4_ALIAS = [string]$Meta.alias
|
| 56 |
+
$env:GEMMA4_LORA_SCALE = '0.45'
|
| 57 |
+
$env:GEMMA4_MAX_CONTEXT = '131072'
|
| 58 |
+
$env:GEMMA4_MAX_IMAGE_TOKENS = '1120'
|
| 59 |
+
$env:GEMMA4_MAX_GPU_MEMORY = [string]$Meta.max_gpu_memory
|
| 60 |
+
$env:GEMMA4_MAX_CPU_MEMORY = '48GiB'
|
| 61 |
+
|
| 62 |
+
$Process = Start-Process -FilePath $Python -ArgumentList @('-u', $Server) -WorkingDirectory $Package `
|
| 63 |
+
-WindowStyle Hidden -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr -PassThru
|
| 64 |
+
$Deadline = (Get-Date).AddMinutes(8)
|
| 65 |
+
$Health = $null
|
| 66 |
+
do {
|
| 67 |
+
Start-Sleep -Seconds 2
|
| 68 |
+
try { $Health = Invoke-RestMethod "http://127.0.0.1:$Port/health" -TimeoutSec 3 } catch { $Health = $null }
|
| 69 |
+
if ($Process.HasExited -and $Health.status -ne 'ok') {
|
| 70 |
+
$Tail = if (Test-Path -LiteralPath $Stderr) { (Get-Content -LiteralPath $Stderr -Tail 120) -join "`n" } else { '' }
|
| 71 |
+
throw "package service exited`n$Tail"
|
| 72 |
+
}
|
| 73 |
+
} until (($Health.status -eq 'ok') -or ((Get-Date) -ge $Deadline))
|
| 74 |
+
if ($Health.status -ne 'ok') { throw 'Package service readiness timeout' }
|
| 75 |
+
|
| 76 |
+
$Live = Get-NetTCPConnection -State Listen -LocalPort $Port | Select-Object -First 1
|
| 77 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Live.OwningProcess)"
|
| 78 |
+
if ($Owner.CommandLine -notlike "*$Server*" -or $Health.model -ne $Meta.alias -or
|
| 79 |
+
[int]$Health.context_tokens -ne 131072 -or [double]$Health.lora_scale -ne 0.45 -or
|
| 80 |
+
[int]$Health.lora_modules -ne 328 -or [int]$Health.adapter_tensor_count -ne 656) {
|
| 81 |
+
throw 'Live package validation failed'
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
[ordered]@{
|
| 85 |
+
state = 'healthy'
|
| 86 |
+
variant = $Meta.variant
|
| 87 |
+
alias = $Health.model
|
| 88 |
+
backend = $Health.backend
|
| 89 |
+
modalities = @($Health.modalities)
|
| 90 |
+
context_tokens = [int]$Health.context_tokens
|
| 91 |
+
image_max_soft_tokens = [int]$Health.image_max_soft_tokens
|
| 92 |
+
lora_scale = [double]$Health.lora_scale
|
| 93 |
+
lora_modules = [int]$Health.lora_modules
|
| 94 |
+
adapter_tensor_count = [int]$Health.adapter_tensor_count
|
| 95 |
+
pid = [int]$Live.OwningProcess
|
| 96 |
+
port = $Port
|
| 97 |
+
command = $Owner.CommandLine
|
| 98 |
+
} | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $State -Encoding UTF8
|
| 99 |
+
Get-Content -LiteralPath $State -Raw
|
serve.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
import json
|
| 5 |
+
import hashlib
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import tempfile
|
| 9 |
+
import threading
|
| 10 |
+
import time
|
| 11 |
+
import traceback
|
| 12 |
+
import types
|
| 13 |
+
import urllib.parse
|
| 14 |
+
from datetime import datetime, timezone
|
| 15 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
from typing import Any
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
from peft import PeftModel, get_peft_model_state_dict
|
| 21 |
+
from peft.tuners.lora.layer import LoraLayer
|
| 22 |
+
from safetensors import safe_open
|
| 23 |
+
from transformers import AutoModelForMultimodalLM, AutoProcessor
|
| 24 |
+
from transformers.video_utils import load_video
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
MODEL_DIR = Path(os.environ["GEMMA4_MODEL_DIR"])
|
| 28 |
+
ADAPTER_DIR = Path(os.environ["GEMMA4_ADAPTER_DIR"])
|
| 29 |
+
TEMPLATE_PATH = Path(os.environ["GEMMA4_TEMPLATE_PATH"])
|
| 30 |
+
STATE_PATH = Path(os.environ["GEMMA4_STATE_PATH"])
|
| 31 |
+
HOST = os.environ.get("GEMMA4_HOST", "127.0.0.1")
|
| 32 |
+
PORT = int(os.environ.get("GEMMA4_PORT", "8091"))
|
| 33 |
+
ALIAS = os.environ.get(
|
| 34 |
+
"GEMMA4_ALIAS", "anru-human10mb-selected-native-multimodal-128k-mix045"
|
| 35 |
+
)
|
| 36 |
+
LORA_SCALE = float(os.environ.get("GEMMA4_LORA_SCALE", "0.45"))
|
| 37 |
+
MAX_CONTEXT = int(os.environ.get("GEMMA4_MAX_CONTEXT", "131072"))
|
| 38 |
+
MAX_IMAGE_TOKENS = int(os.environ.get("GEMMA4_MAX_IMAGE_TOKENS", "1120"))
|
| 39 |
+
MAX_GPU_MEMORY = os.environ.get("GEMMA4_MAX_GPU_MEMORY", "14GiB")
|
| 40 |
+
MAX_CPU_MEMORY = os.environ.get("GEMMA4_MAX_CPU_MEMORY", "48GiB")
|
| 41 |
+
OFFLOAD_DIR = Path(os.environ["GEMMA4_OFFLOAD_DIR"])
|
| 42 |
+
ATTN_IMPLEMENTATION = os.environ.get("GEMMA4_ATTN_IMPLEMENTATION", "sdpa")
|
| 43 |
+
SYSTEM_PROMPT_PATH = Path(
|
| 44 |
+
os.environ.get("GEMMA4_SYSTEM_PROMPT_PATH", str(Path(__file__).with_name("system_prompt.md")))
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
for required in (MODEL_DIR, ADAPTER_DIR, TEMPLATE_PATH, SYSTEM_PROMPT_PATH):
|
| 49 |
+
if not required.exists():
|
| 50 |
+
raise FileNotFoundError(required)
|
| 51 |
+
OFFLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
| 52 |
+
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 53 |
+
DEFAULT_SYSTEM_PROMPT = SYSTEM_PROMPT_PATH.read_text(encoding="utf-8-sig").strip()
|
| 54 |
+
if not DEFAULT_SYSTEM_PROMPT:
|
| 55 |
+
raise ValueError(f"system prompt is empty: {SYSTEM_PROMPT_PATH}")
|
| 56 |
+
SYSTEM_PROMPT_BYTES = SYSTEM_PROMPT_PATH.read_bytes()
|
| 57 |
+
SYSTEM_PROMPT_SHA256 = hashlib.sha256(SYSTEM_PROMPT_BYTES).hexdigest().upper()
|
| 58 |
+
SYSTEM_PROMPT_LINE_COUNT = len(DEFAULT_SYSTEM_PROMPT.splitlines())
|
| 59 |
+
|
| 60 |
+
started_at = time.perf_counter()
|
| 61 |
+
print(f"[{datetime.now(timezone.utc).isoformat()}] loading processor", flush=True)
|
| 62 |
+
processor = AutoProcessor.from_pretrained(MODEL_DIR, local_files_only=True)
|
| 63 |
+
processor.chat_template = TEMPLATE_PATH.read_text(encoding="utf-8")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _fetch_videos_pyav(self, video_url_or_urls, sample_indices_fn=None):
|
| 67 |
+
if isinstance(video_url_or_urls, list):
|
| 68 |
+
return list(
|
| 69 |
+
zip(*[_fetch_videos_pyav(self, item, sample_indices_fn=sample_indices_fn) for item in video_url_or_urls])
|
| 70 |
+
)
|
| 71 |
+
return load_video(video_url_or_urls, backend="pyav", sample_indices_fn=sample_indices_fn)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if getattr(processor, "video_processor", None) is not None:
|
| 75 |
+
processor.video_processor.fetch_videos = types.MethodType(_fetch_videos_pyav, processor.video_processor)
|
| 76 |
+
|
| 77 |
+
print(f"[{datetime.now(timezone.utc).isoformat()}] loading Gemma4 multimodal weights", flush=True)
|
| 78 |
+
model = AutoModelForMultimodalLM.from_pretrained(
|
| 79 |
+
MODEL_DIR,
|
| 80 |
+
dtype=torch.bfloat16,
|
| 81 |
+
device_map="auto",
|
| 82 |
+
max_memory={0: MAX_GPU_MEMORY, "cpu": MAX_CPU_MEMORY},
|
| 83 |
+
offload_folder=str(OFFLOAD_DIR),
|
| 84 |
+
offload_buffers=True,
|
| 85 |
+
low_cpu_mem_usage=True,
|
| 86 |
+
local_files_only=True,
|
| 87 |
+
attn_implementation=ATTN_IMPLEMENTATION,
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
print(f"[{datetime.now(timezone.utc).isoformat()}] attaching LoRA adapter", flush=True)
|
| 91 |
+
model = PeftModel.from_pretrained(
|
| 92 |
+
model,
|
| 93 |
+
ADAPTER_DIR,
|
| 94 |
+
is_trainable=False,
|
| 95 |
+
local_files_only=True,
|
| 96 |
+
key_mapping={r"^model\.": "model.language_model."},
|
| 97 |
+
device_map="auto",
|
| 98 |
+
max_memory={0: MAX_GPU_MEMORY, "cpu": MAX_CPU_MEMORY},
|
| 99 |
+
offload_folder=str(OFFLOAD_DIR),
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
lora_modules = 0
|
| 103 |
+
for module in model.modules():
|
| 104 |
+
if isinstance(module, LoraLayer) and "default" in module.active_adapters:
|
| 105 |
+
module.scale_layer(LORA_SCALE)
|
| 106 |
+
lora_modules += 1
|
| 107 |
+
if not lora_modules:
|
| 108 |
+
raise RuntimeError("LoRA adapter attached zero modules")
|
| 109 |
+
|
| 110 |
+
adapter_state = get_peft_model_state_dict(model, adapter_name="default")
|
| 111 |
+
adapter_tensor_count = len(adapter_state)
|
| 112 |
+
adapter_file = ADAPTER_DIR / "adapter_model.safetensors"
|
| 113 |
+
with safe_open(adapter_file, framework="pt", device="cpu") as adapter_reader:
|
| 114 |
+
source_adapter_tensor_count = len(adapter_reader.keys())
|
| 115 |
+
adapter_nonzero_tensors = sum(
|
| 116 |
+
int(torch.count_nonzero(adapter_reader.get_tensor(key)).item() > 0)
|
| 117 |
+
for key in adapter_reader.keys()
|
| 118 |
+
)
|
| 119 |
+
if adapter_tensor_count == 0 or adapter_nonzero_tensors == 0:
|
| 120 |
+
raise RuntimeError("LoRA adapter state is empty")
|
| 121 |
+
if adapter_tensor_count != source_adapter_tensor_count:
|
| 122 |
+
raise RuntimeError(
|
| 123 |
+
f"LoRA tensor count mismatch: runtime={adapter_tensor_count}, source={source_adapter_tensor_count}"
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
model.eval()
|
| 127 |
+
generation_lock = threading.Lock()
|
| 128 |
+
loaded_seconds = round(time.perf_counter() - started_at, 3)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _json_bytes(value: Any) -> bytes:
|
| 132 |
+
return json.dumps(value, ensure_ascii=False).encode("utf-8")
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _data_url_to_file(url: str, temp_paths: list[Path]) -> str:
|
| 136 |
+
match = re.fullmatch(r"data:([^;,]+)?(?:;charset=[^;,]+)?;base64,(.+)", url, re.DOTALL)
|
| 137 |
+
if not match:
|
| 138 |
+
return url
|
| 139 |
+
mime = match.group(1) or "application/octet-stream"
|
| 140 |
+
suffixes = {
|
| 141 |
+
"image/jpeg": ".jpg",
|
| 142 |
+
"image/png": ".png",
|
| 143 |
+
"image/webp": ".webp",
|
| 144 |
+
"audio/wav": ".wav",
|
| 145 |
+
"audio/mpeg": ".mp3",
|
| 146 |
+
"video/mp4": ".mp4",
|
| 147 |
+
}
|
| 148 |
+
suffix = suffixes.get(mime, ".bin")
|
| 149 |
+
fd, name = tempfile.mkstemp(prefix="gemma4-input-", suffix=suffix)
|
| 150 |
+
os.close(fd)
|
| 151 |
+
path = Path(name)
|
| 152 |
+
path.write_bytes(base64.b64decode(match.group(2), validate=True))
|
| 153 |
+
temp_paths.append(path)
|
| 154 |
+
return str(path)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def _normalise_url(value: Any, temp_paths: list[Path]) -> str:
|
| 158 |
+
if isinstance(value, dict):
|
| 159 |
+
value = value.get("url")
|
| 160 |
+
if not isinstance(value, str) or not value:
|
| 161 |
+
raise ValueError("multimodal content is missing its URL/data")
|
| 162 |
+
if value.startswith("data:"):
|
| 163 |
+
return _data_url_to_file(value, temp_paths)
|
| 164 |
+
if value.startswith("file://"):
|
| 165 |
+
return urllib.parse.unquote(urllib.parse.urlparse(value).path.lstrip("/") if os.name == "nt" else urllib.parse.urlparse(value).path)
|
| 166 |
+
return value
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def _normalise_messages(
|
| 170 |
+
messages: Any, temp_paths: list[Path], use_default_system_prompt: bool
|
| 171 |
+
) -> list[dict[str, Any]]:
|
| 172 |
+
if not isinstance(messages, list) or not messages:
|
| 173 |
+
raise ValueError("messages must be a non-empty list")
|
| 174 |
+
result: list[dict[str, Any]] = []
|
| 175 |
+
for message in messages:
|
| 176 |
+
role = message.get("role")
|
| 177 |
+
content = message.get("content", "")
|
| 178 |
+
if role not in {"system", "user", "assistant"}:
|
| 179 |
+
raise ValueError(f"unsupported role: {role!r}")
|
| 180 |
+
if isinstance(content, str):
|
| 181 |
+
result.append({"role": role, "content": content})
|
| 182 |
+
continue
|
| 183 |
+
if not isinstance(content, list):
|
| 184 |
+
raise ValueError("message content must be text or a content-part list")
|
| 185 |
+
parts: list[dict[str, Any]] = []
|
| 186 |
+
for part in content:
|
| 187 |
+
kind = part.get("type")
|
| 188 |
+
if kind in {"text", "input_text"}:
|
| 189 |
+
parts.append({"type": "text", "text": str(part.get("text", ""))})
|
| 190 |
+
elif kind in {"image", "image_url", "input_image"}:
|
| 191 |
+
source = part.get("url", part.get("image_url", part.get("image")))
|
| 192 |
+
parts.append({"type": "image", "url": _normalise_url(source, temp_paths)})
|
| 193 |
+
elif kind in {"audio", "audio_url", "input_audio"}:
|
| 194 |
+
source = part.get("url", part.get("audio_url", part.get("audio")))
|
| 195 |
+
if source is None and isinstance(part.get("input_audio"), dict):
|
| 196 |
+
audio = part["input_audio"]
|
| 197 |
+
fmt = audio.get("format", "wav")
|
| 198 |
+
source = f"data:audio/{fmt};base64,{audio.get('data', '')}"
|
| 199 |
+
parts.append({"type": "audio", "url": _normalise_url(source, temp_paths)})
|
| 200 |
+
elif kind in {"video", "video_url", "input_video"}:
|
| 201 |
+
source = part.get("url", part.get("video_url", part.get("video")))
|
| 202 |
+
parts.append({"type": "video", "url": _normalise_url(source, temp_paths)})
|
| 203 |
+
else:
|
| 204 |
+
raise ValueError(f"unsupported content part: {kind!r}")
|
| 205 |
+
result.append({"role": role, "content": parts})
|
| 206 |
+
if use_default_system_prompt:
|
| 207 |
+
if result and result[0]["role"] == "system":
|
| 208 |
+
existing = result[0]["content"]
|
| 209 |
+
if isinstance(existing, str):
|
| 210 |
+
merged = DEFAULT_SYSTEM_PROMPT + "\n\n---\n\n" + existing
|
| 211 |
+
else:
|
| 212 |
+
merged = [{"type": "text", "text": DEFAULT_SYSTEM_PROMPT}, *existing]
|
| 213 |
+
result[0] = {"role": "system", "content": merged}
|
| 214 |
+
else:
|
| 215 |
+
result.insert(0, {"role": "system", "content": DEFAULT_SYSTEM_PROMPT})
|
| 216 |
+
return result
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _complete(payload: dict[str, Any]) -> dict[str, Any]:
|
| 220 |
+
temp_paths: list[Path] = []
|
| 221 |
+
began = time.perf_counter()
|
| 222 |
+
try:
|
| 223 |
+
use_default_system_prompt = payload.get("use_default_system_prompt", True)
|
| 224 |
+
if not isinstance(use_default_system_prompt, bool):
|
| 225 |
+
raise ValueError("use_default_system_prompt must be a JSON boolean")
|
| 226 |
+
messages = _normalise_messages(
|
| 227 |
+
payload.get("messages"), temp_paths, use_default_system_prompt
|
| 228 |
+
)
|
| 229 |
+
enable_thinking = bool(payload.get("enable_thinking", False))
|
| 230 |
+
inputs = processor.apply_chat_template(
|
| 231 |
+
messages,
|
| 232 |
+
tokenize=True,
|
| 233 |
+
return_dict=True,
|
| 234 |
+
return_tensors="pt",
|
| 235 |
+
add_generation_prompt=True,
|
| 236 |
+
enable_thinking=enable_thinking,
|
| 237 |
+
processor_kwargs={"images_kwargs": {"max_soft_tokens": MAX_IMAGE_TOKENS}},
|
| 238 |
+
)
|
| 239 |
+
prompt_tokens = int(inputs["input_ids"].shape[-1])
|
| 240 |
+
max_new_tokens = max(1, min(int(payload.get("max_tokens", payload.get("max_completion_tokens", 256))), 4096))
|
| 241 |
+
if prompt_tokens + max_new_tokens > MAX_CONTEXT:
|
| 242 |
+
raise ValueError(
|
| 243 |
+
f"requested {prompt_tokens + max_new_tokens} tokens exceeds {MAX_CONTEXT}-token service context"
|
| 244 |
+
)
|
| 245 |
+
temperature = float(payload.get("temperature", 0.0))
|
| 246 |
+
top_p = float(payload.get("top_p", 0.95))
|
| 247 |
+
inputs = inputs.to(model.device)
|
| 248 |
+
generate_args: dict[str, Any] = {
|
| 249 |
+
"max_new_tokens": max_new_tokens,
|
| 250 |
+
"do_sample": temperature > 0.0,
|
| 251 |
+
"use_cache": True,
|
| 252 |
+
}
|
| 253 |
+
if temperature > 0.0:
|
| 254 |
+
generate_args.update(temperature=temperature, top_p=top_p)
|
| 255 |
+
with generation_lock, torch.inference_mode():
|
| 256 |
+
generated = model.generate(**inputs, **generate_args)
|
| 257 |
+
completion_tokens = int(generated.shape[-1] - prompt_tokens)
|
| 258 |
+
text = processor.decode(generated[0][prompt_tokens:], skip_special_tokens=True)
|
| 259 |
+
for stop in payload.get("stop", []) if isinstance(payload.get("stop", []), list) else [payload.get("stop")]:
|
| 260 |
+
if stop and stop in text:
|
| 261 |
+
text = text.split(stop, 1)[0]
|
| 262 |
+
elapsed = round(time.perf_counter() - began, 3)
|
| 263 |
+
return {
|
| 264 |
+
"id": f"chatcmpl-native-{int(time.time() * 1000)}",
|
| 265 |
+
"object": "chat.completion",
|
| 266 |
+
"created": int(time.time()),
|
| 267 |
+
"model": ALIAS,
|
| 268 |
+
"choices": [
|
| 269 |
+
{
|
| 270 |
+
"index": 0,
|
| 271 |
+
"message": {"role": "assistant", "content": text},
|
| 272 |
+
"finish_reason": "stop" if completion_tokens < max_new_tokens else "length",
|
| 273 |
+
}
|
| 274 |
+
],
|
| 275 |
+
"usage": {
|
| 276 |
+
"prompt_tokens": prompt_tokens,
|
| 277 |
+
"completion_tokens": completion_tokens,
|
| 278 |
+
"total_tokens": prompt_tokens + completion_tokens,
|
| 279 |
+
},
|
| 280 |
+
"timing": {"elapsed_seconds": elapsed},
|
| 281 |
+
"system_prompt": {
|
| 282 |
+
"default_injected": use_default_system_prompt,
|
| 283 |
+
"sha256": SYSTEM_PROMPT_SHA256 if use_default_system_prompt else None,
|
| 284 |
+
},
|
| 285 |
+
}
|
| 286 |
+
finally:
|
| 287 |
+
for path in temp_paths:
|
| 288 |
+
try:
|
| 289 |
+
path.unlink()
|
| 290 |
+
except OSError:
|
| 291 |
+
pass
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
health = {
|
| 295 |
+
"status": "ok",
|
| 296 |
+
"backend": "transformers-native",
|
| 297 |
+
"model": ALIAS,
|
| 298 |
+
"modalities": ["text", "image", "audio", "video"],
|
| 299 |
+
"context_tokens": MAX_CONTEXT,
|
| 300 |
+
"attention_implementation": ATTN_IMPLEMENTATION,
|
| 301 |
+
"default_system_prompt": True,
|
| 302 |
+
"system_prompt_file": SYSTEM_PROMPT_PATH.name,
|
| 303 |
+
"system_prompt_sha256": SYSTEM_PROMPT_SHA256,
|
| 304 |
+
"system_prompt_bytes": len(SYSTEM_PROMPT_BYTES),
|
| 305 |
+
"system_prompt_chars": len(DEFAULT_SYSTEM_PROMPT),
|
| 306 |
+
"system_prompt_lines": SYSTEM_PROMPT_LINE_COUNT,
|
| 307 |
+
"image_max_soft_tokens": MAX_IMAGE_TOKENS,
|
| 308 |
+
"lora_scale": LORA_SCALE,
|
| 309 |
+
"lora_modules": lora_modules,
|
| 310 |
+
"adapter_tensor_count": adapter_tensor_count,
|
| 311 |
+
"adapter_nonzero_tensors": adapter_nonzero_tensors,
|
| 312 |
+
"load_seconds": loaded_seconds,
|
| 313 |
+
"cuda_available": torch.cuda.is_available(),
|
| 314 |
+
"cuda_allocated_bytes": torch.cuda.memory_allocated() if torch.cuda.is_available() else 0,
|
| 315 |
+
"device_map": getattr(model, "hf_device_map", None),
|
| 316 |
+
}
|
| 317 |
+
STATE_PATH.write_text(json.dumps(health, ensure_ascii=False, indent=2), encoding="utf-8")
|
| 318 |
+
print(json.dumps(health, ensure_ascii=False), flush=True)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
class Handler(BaseHTTPRequestHandler):
|
| 322 |
+
server_version = "Gemma4Native/1.0"
|
| 323 |
+
|
| 324 |
+
def _send(self, status: int, value: Any) -> None:
|
| 325 |
+
body = _json_bytes(value)
|
| 326 |
+
self.send_response(status)
|
| 327 |
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
| 328 |
+
self.send_header("Content-Length", str(len(body)))
|
| 329 |
+
self.end_headers()
|
| 330 |
+
self.wfile.write(body)
|
| 331 |
+
|
| 332 |
+
def do_GET(self) -> None: # noqa: N802
|
| 333 |
+
path = urllib.parse.urlparse(self.path).path
|
| 334 |
+
if path == "/health":
|
| 335 |
+
self._send(200, health)
|
| 336 |
+
elif path == "/v1/models":
|
| 337 |
+
self._send(200, {"object": "list", "data": [{"id": ALIAS, "object": "model", "owned_by": "local"}]})
|
| 338 |
+
elif path == "/props":
|
| 339 |
+
self._send(
|
| 340 |
+
200,
|
| 341 |
+
{
|
| 342 |
+
"model_alias": ALIAS,
|
| 343 |
+
"total_slots": 1,
|
| 344 |
+
"default_generation_settings": {"n_ctx": MAX_CONTEXT},
|
| 345 |
+
"modalities": health["modalities"],
|
| 346 |
+
"backend": health["backend"],
|
| 347 |
+
},
|
| 348 |
+
)
|
| 349 |
+
elif path == "/lora-adapters":
|
| 350 |
+
self._send(200, [{"id": 0, "scale": LORA_SCALE, "path": str(ADAPTER_DIR)}])
|
| 351 |
+
else:
|
| 352 |
+
self._send(404, {"error": {"message": "not found", "type": "invalid_request_error"}})
|
| 353 |
+
|
| 354 |
+
def do_POST(self) -> None: # noqa: N802
|
| 355 |
+
path = urllib.parse.urlparse(self.path).path
|
| 356 |
+
if path != "/v1/chat/completions":
|
| 357 |
+
self._send(404, {"error": {"message": "not found", "type": "invalid_request_error"}})
|
| 358 |
+
return
|
| 359 |
+
try:
|
| 360 |
+
length = int(self.headers.get("Content-Length", "0"))
|
| 361 |
+
payload = json.loads(self.rfile.read(length))
|
| 362 |
+
if payload.get("stream"):
|
| 363 |
+
raise ValueError("streaming is not enabled on this local backend")
|
| 364 |
+
self._send(200, _complete(payload))
|
| 365 |
+
except ValueError as exc:
|
| 366 |
+
self._send(400, {"error": {"message": str(exc), "type": "invalid_request_error"}})
|
| 367 |
+
except Exception as exc:
|
| 368 |
+
traceback.print_exc()
|
| 369 |
+
self._send(500, {"error": {"message": repr(exc), "type": "server_error"}})
|
| 370 |
+
|
| 371 |
+
def log_message(self, fmt: str, *args: Any) -> None:
|
| 372 |
+
print(f"[{datetime.now(timezone.utc).isoformat()}] {self.client_address[0]} {fmt % args}", flush=True)
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
print(f"[{datetime.now(timezone.utc).isoformat()}] listening on http://{HOST}:{PORT}", flush=True)
|
| 376 |
+
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
|
smoke-test-result.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"variant": "full-bf16-original",
|
| 4 |
+
"alias": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 5 |
+
"backend": "transformers-native",
|
| 6 |
+
"context_tokens": 131072,
|
| 7 |
+
"lora_scale": 0.45,
|
| 8 |
+
"text": {
|
| 9 |
+
"literal_output": "4\n\n\n\n\n",
|
| 10 |
+
"prompt_tokens": 23,
|
| 11 |
+
"completion_tokens": 12,
|
| 12 |
+
"seconds": 23.731
|
| 13 |
+
},
|
| 14 |
+
"image": {
|
| 15 |
+
"input": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\test-whoami.png",
|
| 16 |
+
"literal_output": "desktop-nusvlb5\\merlin\n",
|
| 17 |
+
"prompt_tokens": 1120,
|
| 18 |
+
"completion_tokens": 12,
|
| 19 |
+
"seconds": 19.372
|
| 20 |
+
}
|
| 21 |
+
}
|
smoke-test.ps1
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param([int]$Port = 8091)
|
| 2 |
+
$ErrorActionPreference = 'Stop'
|
| 3 |
+
|
| 4 |
+
$Package = $PSScriptRoot
|
| 5 |
+
$Meta = Get-Content -LiteralPath (Join-Path $Package 'package.json') -Raw | ConvertFrom-Json
|
| 6 |
+
$Image = Join-Path $Package 'test-whoami.png'
|
| 7 |
+
$Health = Invoke-RestMethod "http://127.0.0.1:$Port/health" -TimeoutSec 10
|
| 8 |
+
if ($Health.model -ne $Meta.alias) { throw 'The running service is not this package' }
|
| 9 |
+
if (-not [bool]$Health.default_system_prompt -or $Health.system_prompt_sha256 -ne $Meta.system_prompt_sha256) {
|
| 10 |
+
throw 'The packaged 30k system prompt is not active'
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
$IdentityBody = [ordered]@{
|
| 14 |
+
model = $Meta.alias
|
| 15 |
+
messages = @(@{ role = 'user'; content = '请只用一句简短中文回答:你是谁?' })
|
| 16 |
+
temperature = 0
|
| 17 |
+
max_tokens = 64
|
| 18 |
+
} | ConvertTo-Json -Depth 8
|
| 19 |
+
$Identity = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 20 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($IdentityBody)) -TimeoutSec 600
|
| 21 |
+
$LiteralIdentity = [string]$Identity.choices[0].message.content
|
| 22 |
+
if ($LiteralIdentity -notmatch '(?i)安若|Anru') { throw "Identity prompt test failed: $LiteralIdentity" }
|
| 23 |
+
if (-not [bool]$Identity.system_prompt.default_injected -or [int]$Identity.usage.prompt_tokens -lt 1000) {
|
| 24 |
+
throw 'Identity request did not inject the packaged 30k prompt'
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
$TextBody = [ordered]@{
|
| 28 |
+
model = $Meta.alias
|
| 29 |
+
use_default_system_prompt = $false
|
| 30 |
+
messages = @(@{ role = 'user'; content = 'Answer only the number: 2+2=' })
|
| 31 |
+
temperature = 0
|
| 32 |
+
max_tokens = 12
|
| 33 |
+
} | ConvertTo-Json -Depth 8
|
| 34 |
+
$Text = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 35 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($TextBody)) -TimeoutSec 300
|
| 36 |
+
if ($Text.choices[0].message.content -notmatch '4') { throw 'Text smoke test failed' }
|
| 37 |
+
|
| 38 |
+
$ImageBody = [ordered]@{
|
| 39 |
+
model = $Meta.alias
|
| 40 |
+
use_default_system_prompt = $false
|
| 41 |
+
messages = @(@{
|
| 42 |
+
role = 'user'
|
| 43 |
+
content = @(
|
| 44 |
+
@{ type = 'image_url'; image_url = @{ url = $Image } },
|
| 45 |
+
@{ type = 'text'; text = 'Transcribe the entire output line immediately below whoami. Include the text before and after the backslash. Output that one line only.' }
|
| 46 |
+
)
|
| 47 |
+
})
|
| 48 |
+
temperature = 0
|
| 49 |
+
max_tokens = 20
|
| 50 |
+
} | ConvertTo-Json -Depth 10
|
| 51 |
+
$ImageResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 52 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($ImageBody)) -TimeoutSec 300
|
| 53 |
+
$LiteralImage = [string]$ImageResult.choices[0].message.content
|
| 54 |
+
if ($LiteralImage -notmatch '(?i)desktop-nusvlb5\\merlin') { throw "Image smoke test failed: $LiteralImage" }
|
| 55 |
+
|
| 56 |
+
$AudioPath = Join-Path $Package 'test-audio.wav'
|
| 57 |
+
$AudioBody = [ordered]@{
|
| 58 |
+
model = $Meta.alias
|
| 59 |
+
use_default_system_prompt = $false
|
| 60 |
+
messages = @(@{
|
| 61 |
+
role = 'user'
|
| 62 |
+
content = @(
|
| 63 |
+
@{ type = 'audio'; audio = $AudioPath },
|
| 64 |
+
@{ type = 'text'; text = 'Write the three digits spoken in this audio, in order. Answer digits only.' }
|
| 65 |
+
)
|
| 66 |
+
})
|
| 67 |
+
temperature = 0
|
| 68 |
+
max_tokens = 12
|
| 69 |
+
} | ConvertTo-Json -Depth 10
|
| 70 |
+
$AudioResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 71 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($AudioBody)) -TimeoutSec 300
|
| 72 |
+
$LiteralAudio = [string]$AudioResult.choices[0].message.content
|
| 73 |
+
if ($LiteralAudio -notmatch '123') { throw "Audio smoke test failed: $LiteralAudio" }
|
| 74 |
+
|
| 75 |
+
$VideoPath = Join-Path $Package 'test-video.mp4'
|
| 76 |
+
$VideoBody = [ordered]@{
|
| 77 |
+
model = $Meta.alias
|
| 78 |
+
use_default_system_prompt = $false
|
| 79 |
+
messages = @(@{
|
| 80 |
+
role = 'user'
|
| 81 |
+
content = @(
|
| 82 |
+
@{ type = 'video'; video = $VideoPath },
|
| 83 |
+
@{ type = 'text'; text = 'What three-digit VIDEO CODE is displayed throughout this video? Answer digits only.' }
|
| 84 |
+
)
|
| 85 |
+
})
|
| 86 |
+
temperature = 0
|
| 87 |
+
max_tokens = 12
|
| 88 |
+
} | ConvertTo-Json -Depth 10
|
| 89 |
+
$VideoResult = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
|
| 90 |
+
-ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($VideoBody)) -TimeoutSec 300
|
| 91 |
+
$LiteralVideo = [string]$VideoResult.choices[0].message.content
|
| 92 |
+
if ($LiteralVideo -notmatch '731') { throw "Video smoke test failed: $LiteralVideo" }
|
| 93 |
+
|
| 94 |
+
$Record = [ordered]@{
|
| 95 |
+
state = 'passed'
|
| 96 |
+
variant = $Meta.variant
|
| 97 |
+
alias = $Meta.alias
|
| 98 |
+
backend = $Health.backend
|
| 99 |
+
context_tokens = [int]$Health.context_tokens
|
| 100 |
+
lora_scale = [double]$Health.lora_scale
|
| 101 |
+
system_prompt = [ordered]@{
|
| 102 |
+
file = [string]$Health.system_prompt_file
|
| 103 |
+
sha256 = [string]$Health.system_prompt_sha256
|
| 104 |
+
bytes = [int]$Health.system_prompt_bytes
|
| 105 |
+
chars = [int]$Health.system_prompt_chars
|
| 106 |
+
lines = [int]$Health.system_prompt_lines
|
| 107 |
+
identity_literal_output = $LiteralIdentity
|
| 108 |
+
prompt_tokens = [int]$Identity.usage.prompt_tokens
|
| 109 |
+
completion_tokens = [int]$Identity.usage.completion_tokens
|
| 110 |
+
seconds = [double]$Identity.timing.elapsed_seconds
|
| 111 |
+
}
|
| 112 |
+
text = [ordered]@{
|
| 113 |
+
literal_output = [string]$Text.choices[0].message.content
|
| 114 |
+
prompt_tokens = [int]$Text.usage.prompt_tokens
|
| 115 |
+
completion_tokens = [int]$Text.usage.completion_tokens
|
| 116 |
+
seconds = [double]$Text.timing.elapsed_seconds
|
| 117 |
+
}
|
| 118 |
+
image = [ordered]@{
|
| 119 |
+
input = $Image
|
| 120 |
+
literal_output = $LiteralImage
|
| 121 |
+
prompt_tokens = [int]$ImageResult.usage.prompt_tokens
|
| 122 |
+
completion_tokens = [int]$ImageResult.usage.completion_tokens
|
| 123 |
+
seconds = [double]$ImageResult.timing.elapsed_seconds
|
| 124 |
+
}
|
| 125 |
+
audio = [ordered]@{
|
| 126 |
+
input = $AudioPath
|
| 127 |
+
literal_output = $LiteralAudio
|
| 128 |
+
prompt_tokens = [int]$AudioResult.usage.prompt_tokens
|
| 129 |
+
completion_tokens = [int]$AudioResult.usage.completion_tokens
|
| 130 |
+
seconds = [double]$AudioResult.timing.elapsed_seconds
|
| 131 |
+
}
|
| 132 |
+
video = [ordered]@{
|
| 133 |
+
input = $VideoPath
|
| 134 |
+
literal_output = $LiteralVideo
|
| 135 |
+
prompt_tokens = [int]$VideoResult.usage.prompt_tokens
|
| 136 |
+
completion_tokens = [int]$VideoResult.usage.completion_tokens
|
| 137 |
+
seconds = [double]$VideoResult.timing.elapsed_seconds
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
$Record | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $Package 'smoke-test-result.json') -Encoding UTF8
|
| 141 |
+
$Record | ConvertTo-Json -Depth 8
|
start.ps1
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[int]$Port = 8091,
|
| 3 |
+
[string]$Python = ''
|
| 4 |
+
)
|
| 5 |
+
$ErrorActionPreference = 'Stop'
|
| 6 |
+
|
| 7 |
+
$Package = $PSScriptRoot
|
| 8 |
+
$Meta = Get-Content -LiteralPath (Join-Path $Package 'package.json') -Raw | ConvertFrom-Json
|
| 9 |
+
$Server = Join-Path $Package 'serve.py'
|
| 10 |
+
$Model = Join-Path $Package 'model'
|
| 11 |
+
$Adapter = Join-Path $Package 'adapter'
|
| 12 |
+
$Template = Join-Path $Model 'chat_template.jinja'
|
| 13 |
+
$Prompt = Join-Path $Package 'system_prompt.md'
|
| 14 |
+
$State = Join-Path $Package 'service-state.json'
|
| 15 |
+
$Stdout = Join-Path $Package 'server.stdout.log'
|
| 16 |
+
$Stderr = Join-Path $Package 'server.stderr.log'
|
| 17 |
+
$Offload = Join-Path $Package 'offload'
|
| 18 |
+
$ReleaseRoot = [IO.Path]::GetFullPath((Join-Path $Package '..'))
|
| 19 |
+
|
| 20 |
+
if (-not $Python) { $Python = $env:ANRU_PYTHON }
|
| 21 |
+
if (-not $Python) {
|
| 22 |
+
$BundledCandidate = [IO.Path]::GetFullPath((Join-Path $Package '..\..\..\work\gemma4-cuda-env\Scripts\python.exe'))
|
| 23 |
+
if (Test-Path -LiteralPath $BundledCandidate -PathType Leaf) { $Python = $BundledCandidate }
|
| 24 |
+
}
|
| 25 |
+
if (-not $Python) {
|
| 26 |
+
$Python = (Get-Command python -ErrorAction Stop).Source
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
foreach ($Path in @($Python, $Server, (Join-Path $Model 'config.json'), (Join-Path $Adapter 'adapter_model.safetensors'), $Template, $Prompt)) {
|
| 30 |
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Missing required file: $Path" }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
$Listener = Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -First 1
|
| 34 |
+
if ($Listener) {
|
| 35 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Listener.OwningProcess)"
|
| 36 |
+
$IsSiblingPackage = $Owner.Name -eq 'python.exe' -and
|
| 37 |
+
$Owner.CommandLine -like "*$ReleaseRoot*serve.py*"
|
| 38 |
+
if ($Owner.Name -eq 'llama-server.exe' -or $Owner.CommandLine -like "*$Server*" -or $IsSiblingPackage) {
|
| 39 |
+
Stop-Process -Id $Listener.OwningProcess -Force
|
| 40 |
+
Wait-Process -Id $Listener.OwningProcess -Timeout 30 -ErrorAction SilentlyContinue
|
| 41 |
+
Start-Sleep -Seconds 2
|
| 42 |
+
} else {
|
| 43 |
+
throw "Port $Port is owned by $($Owner.Name)"
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
New-Item -ItemType Directory -Path $Offload -Force | Out-Null
|
| 48 |
+
Remove-Item -LiteralPath $State, $Stdout, $Stderr -Force -ErrorAction SilentlyContinue
|
| 49 |
+
$env:GEMMA4_MODEL_DIR = $Model
|
| 50 |
+
$env:GEMMA4_ADAPTER_DIR = $Adapter
|
| 51 |
+
$env:GEMMA4_TEMPLATE_PATH = $Template
|
| 52 |
+
$env:GEMMA4_SYSTEM_PROMPT_PATH = $Prompt
|
| 53 |
+
$env:GEMMA4_STATE_PATH = $State
|
| 54 |
+
$env:GEMMA4_OFFLOAD_DIR = $Offload
|
| 55 |
+
$env:GEMMA4_HOST = '127.0.0.1'
|
| 56 |
+
$env:GEMMA4_PORT = "$Port"
|
| 57 |
+
$env:GEMMA4_ALIAS = [string]$Meta.alias
|
| 58 |
+
$env:GEMMA4_LORA_SCALE = '0.45'
|
| 59 |
+
$env:GEMMA4_MAX_CONTEXT = '131072'
|
| 60 |
+
$env:GEMMA4_MAX_IMAGE_TOKENS = '1120'
|
| 61 |
+
$env:GEMMA4_MAX_GPU_MEMORY = [string]$Meta.max_gpu_memory
|
| 62 |
+
$env:GEMMA4_MAX_CPU_MEMORY = '48GiB'
|
| 63 |
+
$env:GEMMA4_ATTN_IMPLEMENTATION = 'sdpa'
|
| 64 |
+
|
| 65 |
+
$Process = Start-Process -FilePath $Python -ArgumentList @('-u', $Server) -WorkingDirectory $Package `
|
| 66 |
+
-WindowStyle Hidden -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr -PassThru
|
| 67 |
+
$Deadline = (Get-Date).AddMinutes(8)
|
| 68 |
+
$Health = $null
|
| 69 |
+
do {
|
| 70 |
+
Start-Sleep -Seconds 2
|
| 71 |
+
try { $Health = Invoke-RestMethod "http://127.0.0.1:$Port/health" -TimeoutSec 3 } catch { $Health = $null }
|
| 72 |
+
if ($Process.HasExited -and $Health.status -ne 'ok') {
|
| 73 |
+
$Tail = if (Test-Path -LiteralPath $Stderr) { (Get-Content -LiteralPath $Stderr -Tail 120) -join "`n" } else { '' }
|
| 74 |
+
throw "package service exited`n$Tail"
|
| 75 |
+
}
|
| 76 |
+
} until (($Health.status -eq 'ok') -or ((Get-Date) -ge $Deadline))
|
| 77 |
+
if ($Health.status -ne 'ok') { throw 'Package service readiness timeout' }
|
| 78 |
+
|
| 79 |
+
$Live = Get-NetTCPConnection -State Listen -LocalPort $Port | Select-Object -First 1
|
| 80 |
+
$Owner = Get-CimInstance Win32_Process -Filter "ProcessId=$($Live.OwningProcess)"
|
| 81 |
+
if ($Owner.CommandLine -notlike "*$Server*" -or $Health.model -ne $Meta.alias -or
|
| 82 |
+
[int]$Health.context_tokens -ne 131072 -or [double]$Health.lora_scale -ne 0.45 -or
|
| 83 |
+
[int]$Health.lora_modules -ne 328 -or [int]$Health.adapter_tensor_count -ne 656 -or
|
| 84 |
+
-not [bool]$Health.default_system_prompt -or $Health.system_prompt_sha256 -ne $Meta.system_prompt_sha256 -or
|
| 85 |
+
$Health.attention_implementation -ne 'sdpa') {
|
| 86 |
+
throw 'Live package validation failed'
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
[ordered]@{
|
| 90 |
+
state = 'healthy'
|
| 91 |
+
variant = $Meta.variant
|
| 92 |
+
alias = $Health.model
|
| 93 |
+
backend = $Health.backend
|
| 94 |
+
modalities = @($Health.modalities)
|
| 95 |
+
context_tokens = [int]$Health.context_tokens
|
| 96 |
+
attention_implementation = [string]$Health.attention_implementation
|
| 97 |
+
default_system_prompt = [bool]$Health.default_system_prompt
|
| 98 |
+
system_prompt_sha256 = [string]$Health.system_prompt_sha256
|
| 99 |
+
system_prompt_chars = [int]$Health.system_prompt_chars
|
| 100 |
+
image_max_soft_tokens = [int]$Health.image_max_soft_tokens
|
| 101 |
+
lora_scale = [double]$Health.lora_scale
|
| 102 |
+
lora_modules = [int]$Health.lora_modules
|
| 103 |
+
adapter_tensor_count = [int]$Health.adapter_tensor_count
|
| 104 |
+
pid = [int]$Live.OwningProcess
|
| 105 |
+
port = $Port
|
| 106 |
+
command = $Owner.CommandLine
|
| 107 |
+
} | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $State -Encoding UTF8
|
| 108 |
+
Get-Content -LiteralPath $State -Raw
|
system_prompt.md
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
test-video.mp4
ADDED
|
Binary file (6.83 kB). View file
|
|
|
test-whoami.png
ADDED
|
verification.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": "passed",
|
| 3 |
+
"variant": "full-bf16-original",
|
| 4 |
+
"alias": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 5 |
+
"backend": "transformers-native",
|
| 6 |
+
"context_tokens": 131072,
|
| 7 |
+
"lora_scale": 0.45,
|
| 8 |
+
"original_weight_bytes": 23919549408,
|
| 9 |
+
"original_weight_sha256": "FE054AE05FF7F44318FD8AE90D58992531455C7ED31356704088F0F2D8C8009A",
|
| 10 |
+
"text": {
|
| 11 |
+
"literal_output": "4\n\n\n\n\n",
|
| 12 |
+
"prompt_tokens": 23,
|
| 13 |
+
"completion_tokens": 12,
|
| 14 |
+
"seconds": 23.731
|
| 15 |
+
},
|
| 16 |
+
"image": {
|
| 17 |
+
"input": "C:\\Users\\夏霖云\\Documents\\Codex\\2026-08-05\\ll-ma\\outputs\\anru-gemma4-12b-native-multimodal-release\\full-bf16\\test-whoami.png",
|
| 18 |
+
"literal_output": "desktop-nusvlb5\\merlin\n",
|
| 19 |
+
"prompt_tokens": 1120,
|
| 20 |
+
"completion_tokens": 12,
|
| 21 |
+
"seconds": 19.372
|
| 22 |
+
},
|
| 23 |
+
"audio": {
|
| 24 |
+
"input": "test-audio.wav",
|
| 25 |
+
"literal_output": "123\n\n\n\n",
|
| 26 |
+
"prompt_tokens": 160,
|
| 27 |
+
"completion_tokens": 12,
|
| 28 |
+
"seconds": 26.382
|
| 29 |
+
},
|
| 30 |
+
"video": {
|
| 31 |
+
"input": "test-video.mp4",
|
| 32 |
+
"literal_output": "731\n",
|
| 33 |
+
"prompt_tokens": 2428,
|
| 34 |
+
"completion_tokens": 6,
|
| 35 |
+
"seconds": 17.99
|
| 36 |
+
},
|
| 37 |
+
"system_prompt_integration": {
|
| 38 |
+
"state": "passed",
|
| 39 |
+
"file": "system_prompt.md",
|
| 40 |
+
"default_injection": true,
|
| 41 |
+
"source_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 42 |
+
"exact_source_copy": true,
|
| 43 |
+
"attention_implementation": "sdpa",
|
| 44 |
+
"actual_identity_inference_variant": "quantized-nf4",
|
| 45 |
+
"actual_identity_literal_output": "我是安若,一个跨性别友好、性别多元友好、创伤知情、重视自主权和生命安全的人工智能陪伴助手。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
|
| 46 |
+
"actual_identity_prompt_tokens": 25149
|
| 47 |
+
},
|
| 48 |
+
"llama_service_integration": {
|
| 49 |
+
"state": "passed",
|
| 50 |
+
"launcher": "Start-Llama-Service.bat",
|
| 51 |
+
"runtime_release": "b10293",
|
| 52 |
+
"runtime_files": 55,
|
| 53 |
+
"runtime_bytes": 701273776,
|
| 54 |
+
"base_sha256": "057C364366301EE285E9026ECF417AAE36F4447D5D9CBBC3591447A9FC77A752",
|
| 55 |
+
"lora_sha256": "3A5060E1CC0C8D4609052EF04A5A60770A64D566E2544B65F6D28F2BA6542037",
|
| 56 |
+
"lora_scale": 0.44999998807907104,
|
| 57 |
+
"context_tokens": 131072,
|
| 58 |
+
"embedded_prompt_sha256": "5591AC2C99FDC6C8E21D1D3B87625BB368204548BEC7B63C3FD6598CF23639BF",
|
| 59 |
+
"identity_literal_output": "我是安若,一个跨性别友好、性别多元友好、创伤知情、尊重自主权和生命安全的人工智能陪伴助手。",
|
| 60 |
+
"identity_prompt_tokens": 25147,
|
| 61 |
+
"identity_wall_seconds": 14.787
|
| 62 |
+
}
|
| 63 |
+
}
|
video-test-result.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "chatcmpl-native-1786015013156",
|
| 3 |
+
"object": "chat.completion",
|
| 4 |
+
"created": 1786015013,
|
| 5 |
+
"model": "anru-gemma4-12b-native-multimodal-bf16-lora045",
|
| 6 |
+
"choices": [
|
| 7 |
+
{
|
| 8 |
+
"index": 0,
|
| 9 |
+
"message": {
|
| 10 |
+
"role": "assistant",
|
| 11 |
+
"content": "731\n"
|
| 12 |
+
},
|
| 13 |
+
"finish_reason": "stop"
|
| 14 |
+
}
|
| 15 |
+
],
|
| 16 |
+
"usage": {
|
| 17 |
+
"prompt_tokens": 2428,
|
| 18 |
+
"completion_tokens": 6,
|
| 19 |
+
"total_tokens": 2434
|
| 20 |
+
},
|
| 21 |
+
"timing": {
|
| 22 |
+
"elapsed_seconds": 17.99
|
| 23 |
+
}
|
| 24 |
+
}
|