chirs345678 commited on
Commit
4e6526a
·
verified ·
1 Parent(s): f14e248

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .gitattributes +11 -30
  2. .gitignore +7 -0
  3. HF_UPLOAD_MANIFEST.json +58 -0
  4. README.md +152 -0
  5. SHA256SUMS.txt +106 -0
  6. Start-Llama-Service.bat +14 -0
  7. Stop-Llama-Service.bat +5 -0
  8. adapter/adapter_config.json +46 -0
  9. adapter/chat_template.jinja +390 -0
  10. adapter/tokenizer_config.json +249 -0
  11. audio-test-result.json +24 -0
  12. llama-bootstrap-verification.json +95 -0
  13. llama-bootstrap.ps1 +224 -0
  14. llama-identity-test.json +58 -0
  15. llama-model/chat_template.prompt30k.jinja +0 -0
  16. llama-model/model-verification.json +11 -0
  17. llama-model/ui-config.json +13 -0
  18. llama-runtime-state.json +20 -0
  19. llama-runtime/ggml.dll +0 -0
  20. llama-runtime/llama-batched-bench-impl.dll +0 -0
  21. llama-runtime/llama-batched-bench.exe +0 -0
  22. llama-runtime/llama-bench.exe +0 -0
  23. llama-runtime/llama-cli.exe +0 -0
  24. llama-runtime/llama-completion.exe +0 -0
  25. llama-runtime/llama-fit-params-impl.dll +0 -0
  26. llama-runtime/llama-fit-params.exe +0 -0
  27. llama-runtime/llama-gemma3-cli.exe +0 -0
  28. llama-runtime/llama-gguf-split.exe +0 -0
  29. llama-runtime/llama-llava-cli.exe +0 -0
  30. llama-runtime/llama-minicpmv-cli.exe +0 -0
  31. llama-runtime/llama-mtmd-cli.exe +0 -0
  32. llama-runtime/llama-mtmd-debug.exe +0 -0
  33. package.json +26 -0
  34. prompt30k-load-test.json +90 -0
  35. prompt30k-postpatch-text-test.json +28 -0
  36. prompt30k-verification.json +184 -0
  37. requirements.txt +10 -0
  38. rollback-llama-bootstrap.ps1 +35 -0
  39. rollback-prompt30k.ps1 +8 -0
  40. rollback/prompt30k-baseline/smoke-test.ps1 +109 -0
  41. rollback/prompt30k-baseline/start.ps1 +99 -0
  42. serve.py +376 -0
  43. smoke-test-result.json +21 -0
  44. smoke-test.ps1 +141 -0
  45. start.ps1 +108 -0
  46. system_prompt.md +0 -0
  47. test-video.mp4 +0 -0
  48. test-whoami.png +0 -0
  49. verification.json +63 -0
  50. video-test-result.json +24 -0
.gitattributes CHANGED
@@ -1,35 +1,16 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
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
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
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
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
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
+ ![Anrn 12B R1 benchmark results](assets/anrn-12b-r1-benchmarks.jpg)
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
+ }