Text Generation
Transformers
Safetensors
English
qwen3_5_text
weight-space-modeling
zero-gradient
risc-v
in-weights-computer
gated-deltanet
math
graphing-calculator
pedagogy
qwen3_5
conversational
Eval Results (legacy)
Instructions to use DJLougen/Qwen3.5-9B-Math-GraphCalc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use DJLougen/Qwen3.5-9B-Math-GraphCalc with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="DJLougen/Qwen3.5-9B-Math-GraphCalc") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("DJLougen/Qwen3.5-9B-Math-GraphCalc") model = AutoModelForCausalLM.from_pretrained("DJLougen/Qwen3.5-9B-Math-GraphCalc", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use DJLougen/Qwen3.5-9B-Math-GraphCalc with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "DJLougen/Qwen3.5-9B-Math-GraphCalc" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "DJLougen/Qwen3.5-9B-Math-GraphCalc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/DJLougen/Qwen3.5-9B-Math-GraphCalc
- SGLang
How to use DJLougen/Qwen3.5-9B-Math-GraphCalc with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "DJLougen/Qwen3.5-9B-Math-GraphCalc" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "DJLougen/Qwen3.5-9B-Math-GraphCalc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "DJLougen/Qwen3.5-9B-Math-GraphCalc" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "DJLougen/Qwen3.5-9B-Math-GraphCalc", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use DJLougen/Qwen3.5-9B-Math-GraphCalc with Docker Model Runner:
docker model run hf.co/DJLougen/Qwen3.5-9B-Math-GraphCalc
Upload Qwen3.5-9B-Math-GraphCalc (43-layer zero-gradient WSP + in-weights RV32 graphing calculator)
Browse files- .gitattributes +8 -0
- README.md +361 -0
- assets/architecture.png +3 -0
- assets/benchmarks.png +3 -0
- assets/example_output.png +3 -0
- assets/hero_banner.png +3 -0
- assets/hf_public_benchmarks.png +3 -0
- assets/in_weights_plots.png +3 -0
- chat_template.jinja +154 -0
- chip_indices.bin.zlib +3 -0
- chip_layout.json +0 -0
- chipformer.json +344 -0
- config.json +105 -0
- firmware/build.sh +36 -0
- firmware/graphcalc.c +2210 -0
- firmware/tables.py +61 -0
- generation_config.json +13 -0
- graphcalc.elf +0 -0
- model.safetensors +3 -0
- runner.py +886 -0
- tokenizer.json +3 -0
- tokenizer_config.json +48 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 36 |
+
assets/architecture.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/benchmarks.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/example_output.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
assets/hero_banner.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
assets/hf_public_benchmarks.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
assets/in_weights_plots.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
chip_indices.bin.zlib filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
license_link: https://huggingface.co/Qwen/Qwen3.5-9B-Base/blob/main/LICENSE
|
| 4 |
+
base_model: Qwen/Qwen3.5-9B-Base
|
| 5 |
+
library_name: transformers
|
| 6 |
+
pipeline_tag: text-generation
|
| 7 |
+
language:
|
| 8 |
+
- en
|
| 9 |
+
tags:
|
| 10 |
+
- weight-space-modeling
|
| 11 |
+
- zero-gradient
|
| 12 |
+
- risc-v
|
| 13 |
+
- in-weights-computer
|
| 14 |
+
- gated-deltanet
|
| 15 |
+
- math
|
| 16 |
+
- graphing-calculator
|
| 17 |
+
- pedagogy
|
| 18 |
+
- qwen3_5
|
| 19 |
+
model-index:
|
| 20 |
+
- name: Qwen3.5-9B-Math-GraphCalc
|
| 21 |
+
results:
|
| 22 |
+
- task:
|
| 23 |
+
type: text-generation
|
| 24 |
+
name: Hard Math & Function Graphing (Disjoint Held-Out)
|
| 25 |
+
dataset:
|
| 26 |
+
name: Carbon-GraphCalc-HeldOut-60
|
| 27 |
+
type: custom
|
| 28 |
+
split: test
|
| 29 |
+
metrics:
|
| 30 |
+
- type: accuracy
|
| 31 |
+
value: 96.67
|
| 32 |
+
name: Math Accuracy (%)
|
| 33 |
+
- type: accuracy
|
| 34 |
+
value: 70.00
|
| 35 |
+
name: Verified Pedagogical Teaching (%)
|
| 36 |
+
- type: accuracy
|
| 37 |
+
value: 97.06
|
| 38 |
+
name: Graph Feature Accuracy (%)
|
| 39 |
+
- type: accuracy
|
| 40 |
+
value: 100.00
|
| 41 |
+
name: Function Plotting Rate (%)
|
| 42 |
+
- type: accuracy
|
| 43 |
+
value: 100.00
|
| 44 |
+
name: In-Weights RV32 Execution Lockstep (%)
|
| 45 |
+
- task:
|
| 46 |
+
type: text-generation
|
| 47 |
+
name: Zero-Shot Math Tutoring (Plain + ChatML Held-Out)
|
| 48 |
+
dataset:
|
| 49 |
+
name: Carbon-MathTutor-HeldOut-180
|
| 50 |
+
type: custom
|
| 51 |
+
split: test
|
| 52 |
+
metrics:
|
| 53 |
+
- type: accuracy
|
| 54 |
+
value: 86.67
|
| 55 |
+
name: Overall Math Accuracy (%)
|
| 56 |
+
- type: accuracy
|
| 57 |
+
value: 80.56
|
| 58 |
+
name: Plain Q&A Math Accuracy (%)
|
| 59 |
+
- type: accuracy
|
| 60 |
+
value: 73.06
|
| 61 |
+
name: Verified Pedagogical Teaching (%)
|
| 62 |
+
- task:
|
| 63 |
+
type: text-generation
|
| 64 |
+
name: GSM8K Official Test (Zero-Shot Greedy)
|
| 65 |
+
dataset:
|
| 66 |
+
name: openai/gsm8k
|
| 67 |
+
type: openai/gsm8k
|
| 68 |
+
config: main
|
| 69 |
+
split: test
|
| 70 |
+
metrics:
|
| 71 |
+
- type: accuracy
|
| 72 |
+
value: 78.85
|
| 73 |
+
name: ChatML Zero-Shot Accuracy (%)
|
| 74 |
+
- type: accuracy
|
| 75 |
+
value: 75.78
|
| 76 |
+
name: Mean Accuracy Plain+ChatML (%)
|
| 77 |
+
- type: accuracy
|
| 78 |
+
value: 53.07
|
| 79 |
+
name: Verified Pedagogical Teaching (%)
|
| 80 |
+
- task:
|
| 81 |
+
type: text-generation
|
| 82 |
+
name: MATH-500 Official Competition Benchmark (Zero-Shot Greedy)
|
| 83 |
+
dataset:
|
| 84 |
+
name: HuggingFaceH4/MATH-500
|
| 85 |
+
type: HuggingFaceH4/MATH-500
|
| 86 |
+
split: test
|
| 87 |
+
metrics:
|
| 88 |
+
- type: accuracy
|
| 89 |
+
value: 58.20
|
| 90 |
+
name: ChatML Zero-Shot Accuracy (%)
|
| 91 |
+
- type: accuracy
|
| 92 |
+
value: 51.80
|
| 93 |
+
name: Mean Accuracy Plain+ChatML (%)
|
| 94 |
+
- type: accuracy
|
| 95 |
+
value: 42.40
|
| 96 |
+
name: Verified Pedagogical Teaching (%)
|
| 97 |
+
- task:
|
| 98 |
+
type: text-generation
|
| 99 |
+
name: AIME 2024 Olympiad Benchmark (Zero-Shot Greedy)
|
| 100 |
+
dataset:
|
| 101 |
+
name: HuggingFaceH4/aime_2024
|
| 102 |
+
type: HuggingFaceH4/aime_2024
|
| 103 |
+
split: train
|
| 104 |
+
metrics:
|
| 105 |
+
- type: accuracy
|
| 106 |
+
value: 10.00
|
| 107 |
+
name: ChatML Zero-Shot Accuracy (%)
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
<p align="center">
|
| 111 |
+
<img src="assets/hero_banner.png" alt="Qwen3.5-9B-Math-GraphCalc — 9.0B Pedagogical Math Tutor + In-Weights RISC-V Graphing Calculator" width="100%">
|
| 112 |
+
</p>
|
| 113 |
+
|
| 114 |
+
<p align="center">
|
| 115 |
+
<a href="https://huggingface.co/Qwen/Qwen3.5-9B-Base"><img src="https://img.shields.io/badge/Base-Qwen3.5--9B--Base-38bdf8?style=flat-square" alt="Base Model"></a>
|
| 116 |
+
<img src="https://img.shields.io/badge/Params-9.0B%20Nonzero%20BF16-10b981?style=flat-square" alt="9.0B Nonzero Params">
|
| 117 |
+
<img src="https://img.shields.io/badge/Gradients-0%20(Pure%20Weight%20Space)-34d399?style=flat-square" alt="Zero Gradients">
|
| 118 |
+
<img src="https://img.shields.io/badge/In--Weights%20CPU-RV32IM%20%2B%20Zba%20%2B%20256b%20Bignum-8b7cf6?style=flat-square" alt="In-Weights CPU">
|
| 119 |
+
</p>
|
| 120 |
+
|
| 121 |
+
**`Qwen3.5-9B-Math-GraphCalc`** (`8,985,670,530` nonzero `bfloat16` parameters = `8.99B`) is an experimental **43-layer** `Qwen3_5ForCausalLM` model built from [`Qwen/Qwen3.5-9B-Base`](https://huggingface.co/Qwen/Qwen3.5-9B-Base) **strictly in weight space**:
|
| 122 |
+
|
| 123 |
+
- **Zero gradients** (`torch.set_grad_enabled(False)`), **zero optimizer steps**, and **zero fitting of weights to recorded activations**.
|
| 124 |
+
- **Zero external Python `eval()`, SymPy, or host calculator servers**: 11 compiled Gated-DeltaNet transformer layers (`layers[0..10]`) sit directly in front of Qwen's 32 widened language layers (`layers[11..42]`, `hidden_size = 5632`, `m = 22`) and execute bare-metal RISC-V instructions (`graphcalc.elf`, `87,636` bytes) using Qwen3.5's own Gated-DeltaNet recurrent state as RAM.
|
| 125 |
+
- When asked a multi-digit calculation, algebraic equation, definite integral, or function-graphing question, the model emits `<tool_call>` at token 0, computes exact 256-bit bignums, reduced fractions, 40-iteration CORDIC trig/log values, roots, turning points, Simpson definite integrals, and coordinate samples **inside its forward pass**, and then teaches the solution step by step.
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## Example Output — 9B Tutor + In-Weights Graphing Calculator
|
| 130 |
+
|
| 131 |
+
### Example 1: Function Graphing, Roots & Extrema Walkthrough (`169,027` In-Weights RV32 Steps)
|
| 132 |
+
|
| 133 |
+
> **User Prompt:** `"Graph y = x^3 - 3*x from x = -2 to 2, identify its roots and turning points, and state the local maximum y-value."`
|
| 134 |
+
|
| 135 |
+
1. **Token 0 — Language layers (`layers[11..42]`) emit `<tool_call>`:**
|
| 136 |
+
```xml
|
| 137 |
+
<tool_call>
|
| 138 |
+
<function=graphcalc>
|
| 139 |
+
<parameter=expression>
|
| 140 |
+
x^3 - 3*x
|
| 141 |
+
</parameter>
|
| 142 |
+
<parameter=xmin>
|
| 143 |
+
-2
|
| 144 |
+
</parameter>
|
| 145 |
+
<parameter=xmax>
|
| 146 |
+
2
|
| 147 |
+
</parameter>
|
| 148 |
+
</function>
|
| 149 |
+
</tool_call>
|
| 150 |
+
```
|
| 151 |
+
2. **In-Weights RISC-V Execution — `layers[0..10]` run `graphcalc.elf` inside Gated-DeltaNet RAM and emit `<tool_response>`:**
|
| 152 |
+
```text
|
| 153 |
+
function: f(x) = x^3 - 3*x on [-2, 2]
|
| 154 |
+
roots: x = -1.732051, x = 0, x = 1.732051
|
| 155 |
+
local max: (-1, 2) | local min: (1, -2)
|
| 156 |
+
y-intercept: (0, 0) | integral: 0
|
| 157 |
+
table: (-2, -2), (-1.3333, 1.6296), (-0.6667, 1.7037), (0, 0), (0.6667, -1.7037), (1.3333, -1.6296), (2, 2)
|
| 158 |
+
```
|
| 159 |
+
<p align="center">
|
| 160 |
+
<img src="assets/example_output.png" alt="Rendered curve, roots, extrema and 7-point sample table computed inside layers[0..10] for f(x) = x^3 - 3x" width="100%">
|
| 161 |
+
</p>
|
| 162 |
+
3. **Verbatim 9B Step-by-Step Tutor Walkthrough — `layers[11..42]` read `<tool_response>` and teach the solution:**
|
| 163 |
+
> ## 1. Graphing the Function
|
| 164 |
+
>
|
| 165 |
+
> Let's first graph the function y = x³ - 3x from x = -2 to x = 2:
|
| 166 |
+
>
|
| 167 |
+
> The graph shows a cubic function with:
|
| 168 |
+
> - A local maximum at (-1, 2)
|
| 169 |
+
> - A local minimum at (1, -2)
|
| 170 |
+
> - Passing through the origin (0, 0)
|
| 171 |
+
> - Symmetric about the origin (odd function)
|
| 172 |
+
>
|
| 173 |
+
> ## 2. Finding the Roots
|
| 174 |
+
>
|
| 175 |
+
> The roots are the x-values where y = 0. From the graph, we can see the function crosses the x-axis at three points:
|
| 176 |
+
>
|
| 177 |
+
> **Roots:**
|
| 178 |
+
> - x = -1.732051 (approximately -√3)
|
| 179 |
+
> - x = 0
|
| 180 |
+
> - x = 1.732051 (approximately √3)
|
| 181 |
+
>
|
| 182 |
+
> Let's verify these roots algebraically:
|
| 183 |
+
> x³ - 3x = 0
|
| 184 |
+
> x(x² - 3) = 0
|
| 185 |
+
> x = 0 or x² - 3 = 0
|
| 186 |
+
> x = 0 or x² = 3
|
| 187 |
+
> x = 0 or x = ±√3
|
| 188 |
+
>
|
| 189 |
+
> So the exact roots are x = -√3, x = 0, and x = √3.
|
| 190 |
+
>
|
| 191 |
+
> ## 3. Finding the Turning Points
|
| 192 |
+
>
|
| 193 |
+
> Turning points occur where the derivative equals zero. Let's find the derivative:
|
| 194 |
+
> f(x) = x³ - 3x
|
| 195 |
+
> f'(x) = 3x² - 3
|
| 196 |
+
>
|
| 197 |
+
> Setting f'(x) = 0:
|
| 198 |
+
> 3x² - 3 = 0
|
| 199 |
+
> 3x² = 3
|
| 200 |
+
> x² = 1
|
| 201 |
+
> x = ±1
|
| 202 |
+
>
|
| 203 |
+
> Now let's find the y-values at these x-values:
|
| 204 |
+
> - At x = -1: f(-1) = (-1)³ - 3(-1) = -1 + 3 = 2
|
| 205 |
+
> - At x = 1: f(1) = (1)³ - 3(1) = 1 - 3 = -2
|
| 206 |
+
>
|
| 207 |
+
> So the turning points are:
|
| 208 |
+
> - Local maximum at (-1, 2)
|
| 209 |
+
> - Local minimum at (1, -2)
|
| 210 |
+
>
|
| 211 |
+
> ## 4. Local Maximum y-Value
|
| 212 |
+
>
|
| 213 |
+
> The local maximum occurs at x = -1, and the y-value at this point is 2.
|
| 214 |
+
>
|
| 215 |
+
> **Answer:**
|
| 216 |
+
> - Roots: x = -√3, x = 0, x = √3
|
| 217 |
+
> - Turning points: Local maximum at (-1, 2) and local minimum at (1, -2)
|
| 218 |
+
> - Local maximum y-value: 2
|
| 219 |
+
>
|
| 220 |
+
> The function has a local maximum y-value of 2 at x = -1.
|
| 221 |
+
|
| 222 |
+
### Example 2: Exact 9-Digit Bignum Arithmetic (`15,701` In-Weights RV32 Steps via 256-bit `OP_BN`)
|
| 223 |
+
|
| 224 |
+
> **User Prompt:** `"Calculate 37429 * 8613 + 194820 and explain your steps."`
|
| 225 |
+
|
| 226 |
+
1. **In-Weights RISC-V `<tool_response>` (`layers[0..10]`):**
|
| 227 |
+
```text
|
| 228 |
+
expression: 37429 * 8613 + 194820
|
| 229 |
+
step 1: 37429 * 8613 = 322375977
|
| 230 |
+
step 2: 322375977 + 194820 = 322570797
|
| 231 |
+
exact value: 322570797
|
| 232 |
+
```
|
| 233 |
+
2. **Verbatim 9B Tutor Explanation (`layers[11..42]`):**
|
| 234 |
+
> ## 37429 * 8613 + 194820
|
| 235 |
+
>
|
| 236 |
+
> ## Step 1: Multiply 37429 by 8613
|
| 237 |
+
>
|
| 238 |
+
> Let's break this multiplication down:
|
| 239 |
+
>
|
| 240 |
+
> 37429 × 8613 = 322,375,977
|
| 241 |
+
>
|
| 242 |
+
> ## Step 2: Add 194,820 to the result
|
| 243 |
+
>
|
| 244 |
+
> 322,375,977 + 194,820 = 322,570,797
|
| 245 |
+
>
|
| 246 |
+
> ## Final Answer: 322,570,797
|
| 247 |
+
>
|
| 248 |
+
> The graphing calculator confirmed this exact value. When working with large numbers, it's helpful to break the problem into smaller steps and verify each part.
|
| 249 |
+
|
| 250 |
+
---
|
| 251 |
+
|
| 252 |
+
## Functions Solved, Integrated & Sampled Inside 9B Weights
|
| 253 |
+
|
| 254 |
+
<p align="center">
|
| 255 |
+
<img src="assets/in_weights_plots.png" alt="Functions solved, integrated, and sampled inside 9B transformer weights (layers[0..10])" width="100%">
|
| 256 |
+
</p>
|
| 257 |
+
|
| 258 |
+
Every coordinate, root, extremum, and definite integral above is computed token-by-token inside `layers[0..10]` (`0` mismatches between HF `Qwen3_5DecoderLayer` weights, Triton `Executor2`, `BR.Session`, and numpy circuit IR):
|
| 259 |
+
|
| 260 |
+
| Prompted Function & Domain | In-Weights RV32 Steps | Exact Roots Found | Extrema / Vertex | 48-Panel Simpson `∫ f(x) dx` |
|
| 261 |
+
|---|---:|---|---|---:|
|
| 262 |
+
| `f(x) = x³ − 3x` on `[−2, 2]` | `169,027` | `x = −1.732051, 0, 1.732051` | `max(−1, 2)`, `min(1, −2)` | `0` |
|
| 263 |
+
| `f(x) = 2 sin(x)` on `[0, 6.28]` | `313,365` | `x = 0, 3.141593` | `max(1.570797, 2)`, `min(4.712388, −2)` | `0.00001` |
|
| 264 |
+
| `f(x) = x² − 6x + 5` on `[−1, 7]` | `78,963` | `x = 1, 5` | `vertex (3, −4)` | `10.666667` |
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
## Benchmarks
|
| 269 |
+
|
| 270 |
+
<p align="center">
|
| 271 |
+
<img src="assets/benchmarks.png" alt="9B Held-Out Hard-Math & Function Graphing Benchmark (N = 60 Disjoint Problems)" width="100%">
|
| 272 |
+
</p>
|
| 273 |
+
|
| 274 |
+
### 1. Disjoint Hard-Math & Function Graphing (`N = 60` Unseen Problems, Greedy Decoding)
|
| 275 |
+
|
| 276 |
+
Across the 60 unseen hard-math and function-graphing problems, `Qwen3.5-9B-Math-GraphCalc` executes **`3,195,505` in-weights RV32 instruction token steps** with **`0` lockstep errors**:
|
| 277 |
+
|
| 278 |
+
| Model Arm | WSP Base Edits | Compiled RV32 NNZ | Math Acc | Verified Teaching | Graph Feature Acc | Plot Rate | In-Weights Exec OK |
|
| 279 |
+
|---|---:|---:|---:|---:|---:|---:|---:|
|
| 280 |
+
| `Qwen/Qwen3.5-9B-Base` (`32L`) | `0` | `0` | `73.33%` | `63.33%` | `43.14%` | `0.00%` | `0.00%` |
|
| 281 |
+
| `Qwen3.5-9B-Math-Tutor` (`32L WSP`) | `2,916,073` | `0` | `81.67%` | `75.00%` | `53.92%` | `0.00%` | `0.00%` |
|
| 282 |
+
| **`Qwen3.5-9B-Math-GraphCalc` (`43L`)** | **`2,920,169`** | **`1,045,984`** | **`96.67%`** | **`70.00%`** | **`97.06%`** | **`100.00%`** | **`100.00%`** |
|
| 283 |
+
|
| 284 |
+
### 2. Official Public HuggingFace Math Benchmarks (`7,546` Total Zero-Shot Greedy Evaluations)
|
| 285 |
+
|
| 286 |
+
<p align="center">
|
| 287 |
+
<img src="assets/hf_public_benchmarks.png" alt="Official Public HuggingFace Math Benchmarks — GSM8K, MATH-500, AIME 2024, and MATH-500 Computational-50" width="100%">
|
| 288 |
+
</p>
|
| 289 |
+
|
| 290 |
+
Evaluated zero-shot with greedy decoding directly on official HuggingFace Hub datasets ([`openai/gsm8k`](https://huggingface.co/datasets/openai/gsm8k) full `1,319` test set, [`HuggingFaceH4/MATH-500`](https://huggingface.co/datasets/HuggingFaceH4/MATH-500) full `500` competition set, and [`HuggingFaceH4/aime_2024`](https://huggingface.co/datasets/HuggingFaceH4/aime_2024) full `30` Olympiad set across both raw `Plain Q&A` and `ChatML` formats):
|
| 291 |
+
|
| 292 |
+
| Public HF Dataset | Arm | Mean Math Acc | Plain Q&A Acc | ChatML Acc | Plain Verified Teach | ChatML Verified Teach |
|
| 293 |
+
|---|---|---:|---:|---:|---:|---:|
|
| 294 |
+
| **`openai/gsm8k`** (`N = 1,319 × 2`) | `Qwen3.5-9B-Base` | `78.54%` | `82.79%` | `74.30%` | `51.40%` | `5.31%` |
|
| 295 |
+
| **`openai/gsm8k`** (`N = 1,319 × 2`) | **`9B WSP Tutor / GraphCalc`** | **`75.78%`** | **`72.71%`** | **`78.85%`** | **`59.89%`** | **`53.07%`** |
|
| 296 |
+
| **`HuggingFaceH4/MATH-500`** (`N = 500 × 2`) | `Qwen3.5-9B-Base` | `48.40%` | `40.60%` | `56.20%` | `17.60%` | `2.80%` |
|
| 297 |
+
| **`HuggingFaceH4/MATH-500`** (`N = 500 × 2`) | **`9B WSP Tutor / GraphCalc`** | **`51.80%`** | **`45.40%`** | **`58.20%`** | **`28.00%`** | **`42.40%`** |
|
| 298 |
+
| **`HuggingFaceH4/aime_2024`** (`N = 30 × 2`) | `Qwen3.5-9B-Base` | `6.67%` | `3.33%` | `10.00%` | `0.00%` | `0.00%` |
|
| 299 |
+
| **`HuggingFaceH4/aime_2024`** (`N = 30 × 2`) | **`9B WSP Tutor / GraphCalc`** | **`8.34%`** | **`6.67%`** | **`10.00%`** | **`6.67%`** | **`0.00%`** |
|
| 300 |
+
|
| 301 |
+
#### `MATH-500` Accuracy by Competition Difficulty Level (Zero-Shot ChatML)
|
| 302 |
+
|
| 303 |
+
| Competition Level | `Qwen/Qwen3.5-9B-Base` | **`9B WSP Tutor / GraphCalc`** |
|
| 304 |
+
|---|---:|---:|
|
| 305 |
+
| Level 1 | `88.37%` | **`88.37%`** |
|
| 306 |
+
| Level 2 | `76.67%` | **`77.78%`** |
|
| 307 |
+
| Level 3 | `69.52%` | **`72.38%`** |
|
| 308 |
+
| Level 4 | `48.44%` | **`52.34%`** |
|
| 309 |
+
| Level 5 | `29.10%` | **`29.85%`** |
|
| 310 |
+
|
| 311 |
+
> **Domain Scope (`<tools>` vs. Standard Zero-Shot):** `graphcalc.elf` implements a specialized single-variable graphing & 256-bit arithmetic/CORDIC calculator (`+ - * / ^`, `sin`, `cos`, `tan`, `sqrt`, `ln`, `exp`, roots, extrema, Simpson integrals). Pass `<tools>` for multi-digit arithmetic, exact fractions, calculus, and function graphing (**`96.67%`** accuracy on `GraphCalc-HeldOut-60`). For general symbolic competition math, modular number theory, or multi-variable systems (where forcing a single-variable `<tool_call>` on a 50-item `MATH-500` scalar slice scored `58.00%` vs `72.00%` without `<tools>` across `2,927,967` RV32 steps), omit `<tools>` — the untied `E6` gate triggers **`0 / 360`** false tool calls and runs at full 32-layer speed.
|
| 312 |
+
|
| 313 |
+
### 3. Disjoint Zero-Shot Math Tutoring & Language Retention (`N = 180 × 2 = 360` Generations, No `<tools>`)
|
| 314 |
+
|
| 315 |
+
When prompted without `<tools>`, untied `lm_head` `<tool_call>` calibration (`E6 = 1.3×`) triggers **`0 / 360` false tool calls** while improving zero-shot `plain` Q&A tutoring and preserving `FineWeb-Edu` causal language modeling (`+0.0001` nats):
|
| 316 |
+
|
| 317 |
+
| Model Arm | Overall Math Acc | Plain Q&A Math Acc | ChatML Math Acc | Verified Teaching | `FineWeb-Edu` NLL | False `<tool_call>` |
|
| 318 |
+
|---|---:|---:|---:|---:|---:|---:|
|
| 319 |
+
| `Qwen/Qwen3.5-9B-Base` | `81.67%` | `68.89%` | `94.44%` | `12.78%` | `2.1093` | `0 / 360` |
|
| 320 |
+
| `Qwen3.5-9B-Math-Tutor` (`A3`) | `86.67%` | `80.56%` | `92.78%` | `73.06%` | `2.1094` | `0 / 360` |
|
| 321 |
+
| **`Qwen3.5-9B-Math-GraphCalc` (no `<tools>`)** | **`86.67%`** | **`80.56%`** | **`92.78%`** | **`73.06%`** | **`2.1094`** | **`0 / 360`** |
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
## Architecture — How the 43-Layer Model Works
|
| 326 |
+
|
| 327 |
+
<p align="center">
|
| 328 |
+
<img src="assets/architecture.png" alt="43-Layer Qwen3.5-9B-Math-GraphCalc In-Weights Execution Loop" width="100%">
|
| 329 |
+
</p>
|
| 330 |
+
|
| 331 |
+
1. **Stage 1 — Untied 9B Weight-Space Tutor Surgery (`2,920,169` base scalar edits):**
|
| 332 |
+
- **`E1` (Untied Turn-Header Bridge):** Re-anchors `Question` and `Answer` in `embed_tokens.weight` into Qwen3.5-9B's native ChatML turn subspace with zero output-logit side effects, while damping raw `</think>` / `<|file_sep|>` markup leak in untied `lm_head.weight`.
|
| 333 |
+
- **`E2` & `E3` (Static MLP Subspace Surgery):** Closed-form SVD/projection inspection of `layers[18..31]` damps worksheet continuation, multi-newline collapse, bare MCQ guess, and orthogonalized CJK exam leakage while amplifying clean English step-initiation and deductive-reasoning columns.
|
| 334 |
+
- **`E6` (Untied `<tool_call>` Gate):** Scales `lm_head.weight[<tool_call>]` (`4,096` scalars) by `1.3×` so `<tool_call>` wins rank 1 when `<tools>` is present and stays silent (`0/360`) without `<tools>`.
|
| 335 |
+
2. **Stage 2 — 11-Layer Compiled RISC-V Co-Processor (`1,045,984` nonzero compiled scalars):**
|
| 336 |
+
- Compiles RV32IM + Zba + 256-bit hardware bignum (`OP_BN`) + `CarbonText` bridge (`Target(16, 32, 12288)`) via [`qwenputer`](https://github.com/divagr18/qwenputer) into `layers[0..10]` (`1,265` residual slots, `hidden_size = 5632`, `m = 22`).
|
| 337 |
+
- Because `Qwen3.5-9B-Base` already has `linear_num_value_heads = 32` and `intermediate_size = 12288`, widening `layers[11..42]` requires **zero** MLP or Gated-DeltaNet head expansion — only orthogonal hidden-dimension padding (`4096 -> 5632`) plus analytical ballast muting (`sinks = None`) on `mu_dim`.
|
| 338 |
+
- Untied `lm_head.weight` copies `(*code_dims, tau_dim)` (`15` chip columns) alongside the `4,096` Qwen columns, preserving 100% bit-exact logits across all `478,656` tokens (`head_mismatch = 0`).
|
| 339 |
+
3. **Zero-Padding-Free Compact `model.safetensors` (`8.99B` Nonzero BF16):**
|
| 340 |
+
- Stores only the `8,985,670,530` nonzero `bfloat16` parameters in `model.safetensors` plus lossless `chip_layout.json` + `chip_indices.bin.zlib`, cutting checkpoint size in half (`17.97 GB` instead of `36.42 GB` zero-padded shards) while reconstructing all 43 `Qwen3_5DecoderLayer` modules bit-for-bit (`torch.equal`).
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
## Quickstart
|
| 345 |
+
|
| 346 |
+
```bash
|
| 347 |
+
pip install torch transformers safetensors huggingface_hub tokenizers
|
| 348 |
+
git clone https://github.com/divagr18/qwenputer
|
| 349 |
+
git clone https://huggingface.co/DJLougen/Qwen3.5-9B-Math-GraphCalc
|
| 350 |
+
|
| 351 |
+
# Run end-to-end 43-layer in-weights generation (Triton fast path or --pure-hf un-fused HF layers)
|
| 352 |
+
QWENPUTER_ROOT=./qwenputer python Qwen3.5-9B-Math-GraphCalc/runner.py \
|
| 353 |
+
--ask "Graph y = x^3 - 3*x from x = -2 to 2, identify its roots and turning points, and state the local maximum y-value."
|
| 354 |
+
```
|
| 355 |
+
|
| 356 |
+
---
|
| 357 |
+
|
| 358 |
+
## Credits & License
|
| 359 |
+
|
| 360 |
+
- **Base Model:** [`Qwen/Qwen3.5-9B-Base`](https://huggingface.co/Qwen/Qwen3.5-9B-Base) (Apache 2.0)
|
| 361 |
+
- **RV32 Circuit Compiler Foundation:** Built upon the Gated-DeltaNet RISC-V compiler from [`divagr18/qwenputer`](https://github.com/divagr18/qwenputer), extended with untied 9B `lm_head` synthesis, zero-gradient analytical ballast muting, ChatML bridge tokens (`256..264`), and custom C graphing-calculator firmware (`graphcalc.c`).
|
assets/architecture.png
ADDED
|
Git LFS Details
|
assets/benchmarks.png
ADDED
|
Git LFS Details
|
assets/example_output.png
ADDED
|
Git LFS Details
|
assets/hero_banner.png
ADDED
|
Git LFS Details
|
assets/hf_public_benchmarks.png
ADDED
|
Git LFS Details
|
assets/in_weights_plots.png
ADDED
|
Git LFS Details
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is false %}
|
| 150 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
chip_indices.bin.zlib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:13f715f1f3285e34b1460f56517efe1cf9025a94f1d701c4805772641bdc22d8
|
| 3 |
+
size 45910417
|
chip_layout.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
chipformer.json
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"layout": "rv1-carbon-graphcalc",
|
| 3 |
+
"base_model": "Qwen/Qwen3.5-9B-Base",
|
| 4 |
+
"tie_word_embeddings": false,
|
| 5 |
+
"total_nonzero_params": 8985670530,
|
| 6 |
+
"total_dense_params": 18208957504,
|
| 7 |
+
"wsp_patch": "patch_9b_graphcalc.safetensors",
|
| 8 |
+
"wsp_changed_base_weights": 2920169,
|
| 9 |
+
"compiled_chip_layer_nonzero_weights": 1045984,
|
| 10 |
+
"compiled_embedding_chip_nonzero_weights": 25387170,
|
| 11 |
+
"compiled_lm_head_chip_nonzero_weights": 5321013,
|
| 12 |
+
"firmware": "graphcalc.elf",
|
| 13 |
+
"firmware_elf_bytes": 87636,
|
| 14 |
+
"chip_token_offset": 248320,
|
| 15 |
+
"chip_vocab": 230324,
|
| 16 |
+
"chip_layers": 11,
|
| 17 |
+
"qwen_layers_from": 11,
|
| 18 |
+
"num_total_layers": 43,
|
| 19 |
+
"hidden": 5632,
|
| 20 |
+
"chip_dims_offset": 4096,
|
| 21 |
+
"ballast_dims": 22,
|
| 22 |
+
"circuit": {
|
| 23 |
+
"layers": 11,
|
| 24 |
+
"dims": 2918,
|
| 25 |
+
"heads": 103,
|
| 26 |
+
"ffn_units": 42113,
|
| 27 |
+
"max_units": 8149,
|
| 28 |
+
"max_heads": 16
|
| 29 |
+
},
|
| 30 |
+
"slots": 1265,
|
| 31 |
+
"muted": true,
|
| 32 |
+
"anti_sink": "none (analytical ballast mute; zero fitted activations)",
|
| 33 |
+
"code_dims": [
|
| 34 |
+
4250,
|
| 35 |
+
4251,
|
| 36 |
+
4252,
|
| 37 |
+
4253,
|
| 38 |
+
4254,
|
| 39 |
+
4255,
|
| 40 |
+
4256,
|
| 41 |
+
4257,
|
| 42 |
+
4258,
|
| 43 |
+
4259,
|
| 44 |
+
4260,
|
| 45 |
+
4261,
|
| 46 |
+
4262,
|
| 47 |
+
4263,
|
| 48 |
+
4264,
|
| 49 |
+
4265,
|
| 50 |
+
4266,
|
| 51 |
+
4267,
|
| 52 |
+
4268,
|
| 53 |
+
4269,
|
| 54 |
+
4270
|
| 55 |
+
],
|
| 56 |
+
"tau_dim": 4273,
|
| 57 |
+
"mu_dim": 4272,
|
| 58 |
+
"nfam": 5,
|
| 59 |
+
"npay": 16,
|
| 60 |
+
"tool_call": [
|
| 61 |
+
248058,
|
| 62 |
+
248059
|
| 63 |
+
],
|
| 64 |
+
"specials": [
|
| 65 |
+
"<tool_response>",
|
| 66 |
+
"</tool_response>",
|
| 67 |
+
"<think>",
|
| 68 |
+
"</think>",
|
| 69 |
+
"<|im_end|>",
|
| 70 |
+
"<|im_start|>",
|
| 71 |
+
"user",
|
| 72 |
+
"assistant",
|
| 73 |
+
"\n\n"
|
| 74 |
+
],
|
| 75 |
+
"halt_nl_byte": 32,
|
| 76 |
+
"printable": [
|
| 77 |
+
188,
|
| 78 |
+
189,
|
| 79 |
+
190,
|
| 80 |
+
191,
|
| 81 |
+
192,
|
| 82 |
+
193,
|
| 83 |
+
194,
|
| 84 |
+
195,
|
| 85 |
+
196,
|
| 86 |
+
197,
|
| 87 |
+
198,
|
| 88 |
+
199,
|
| 89 |
+
200,
|
| 90 |
+
201,
|
| 91 |
+
202,
|
| 92 |
+
203,
|
| 93 |
+
204,
|
| 94 |
+
205,
|
| 95 |
+
206,
|
| 96 |
+
207,
|
| 97 |
+
208,
|
| 98 |
+
209,
|
| 99 |
+
210,
|
| 100 |
+
211,
|
| 101 |
+
212,
|
| 102 |
+
213,
|
| 103 |
+
214,
|
| 104 |
+
215,
|
| 105 |
+
216,
|
| 106 |
+
217,
|
| 107 |
+
218,
|
| 108 |
+
219,
|
| 109 |
+
220,
|
| 110 |
+
0,
|
| 111 |
+
1,
|
| 112 |
+
2,
|
| 113 |
+
3,
|
| 114 |
+
4,
|
| 115 |
+
5,
|
| 116 |
+
6,
|
| 117 |
+
7,
|
| 118 |
+
8,
|
| 119 |
+
9,
|
| 120 |
+
10,
|
| 121 |
+
11,
|
| 122 |
+
12,
|
| 123 |
+
13,
|
| 124 |
+
14,
|
| 125 |
+
15,
|
| 126 |
+
16,
|
| 127 |
+
17,
|
| 128 |
+
18,
|
| 129 |
+
19,
|
| 130 |
+
20,
|
| 131 |
+
21,
|
| 132 |
+
22,
|
| 133 |
+
23,
|
| 134 |
+
24,
|
| 135 |
+
25,
|
| 136 |
+
26,
|
| 137 |
+
27,
|
| 138 |
+
28,
|
| 139 |
+
29,
|
| 140 |
+
30,
|
| 141 |
+
31,
|
| 142 |
+
32,
|
| 143 |
+
33,
|
| 144 |
+
34,
|
| 145 |
+
35,
|
| 146 |
+
36,
|
| 147 |
+
37,
|
| 148 |
+
38,
|
| 149 |
+
39,
|
| 150 |
+
40,
|
| 151 |
+
41,
|
| 152 |
+
42,
|
| 153 |
+
43,
|
| 154 |
+
44,
|
| 155 |
+
45,
|
| 156 |
+
46,
|
| 157 |
+
47,
|
| 158 |
+
48,
|
| 159 |
+
49,
|
| 160 |
+
50,
|
| 161 |
+
51,
|
| 162 |
+
52,
|
| 163 |
+
53,
|
| 164 |
+
54,
|
| 165 |
+
55,
|
| 166 |
+
56,
|
| 167 |
+
57,
|
| 168 |
+
58,
|
| 169 |
+
59,
|
| 170 |
+
60,
|
| 171 |
+
61,
|
| 172 |
+
62,
|
| 173 |
+
63,
|
| 174 |
+
64,
|
| 175 |
+
65,
|
| 176 |
+
66,
|
| 177 |
+
67,
|
| 178 |
+
68,
|
| 179 |
+
69,
|
| 180 |
+
70,
|
| 181 |
+
71,
|
| 182 |
+
72,
|
| 183 |
+
73,
|
| 184 |
+
74,
|
| 185 |
+
75,
|
| 186 |
+
76,
|
| 187 |
+
77,
|
| 188 |
+
78,
|
| 189 |
+
79,
|
| 190 |
+
80,
|
| 191 |
+
81,
|
| 192 |
+
82,
|
| 193 |
+
83,
|
| 194 |
+
84,
|
| 195 |
+
85,
|
| 196 |
+
86,
|
| 197 |
+
87,
|
| 198 |
+
88,
|
| 199 |
+
89,
|
| 200 |
+
90,
|
| 201 |
+
91,
|
| 202 |
+
92,
|
| 203 |
+
93,
|
| 204 |
+
221,
|
| 205 |
+
222,
|
| 206 |
+
223,
|
| 207 |
+
224,
|
| 208 |
+
225,
|
| 209 |
+
226,
|
| 210 |
+
227,
|
| 211 |
+
228,
|
| 212 |
+
229,
|
| 213 |
+
230,
|
| 214 |
+
231,
|
| 215 |
+
232,
|
| 216 |
+
233,
|
| 217 |
+
234,
|
| 218 |
+
235,
|
| 219 |
+
236,
|
| 220 |
+
237,
|
| 221 |
+
238,
|
| 222 |
+
239,
|
| 223 |
+
240,
|
| 224 |
+
241,
|
| 225 |
+
242,
|
| 226 |
+
243,
|
| 227 |
+
244,
|
| 228 |
+
245,
|
| 229 |
+
246,
|
| 230 |
+
247,
|
| 231 |
+
248,
|
| 232 |
+
249,
|
| 233 |
+
250,
|
| 234 |
+
251,
|
| 235 |
+
252,
|
| 236 |
+
253,
|
| 237 |
+
254,
|
| 238 |
+
94,
|
| 239 |
+
95,
|
| 240 |
+
96,
|
| 241 |
+
97,
|
| 242 |
+
98,
|
| 243 |
+
99,
|
| 244 |
+
100,
|
| 245 |
+
101,
|
| 246 |
+
102,
|
| 247 |
+
103,
|
| 248 |
+
104,
|
| 249 |
+
105,
|
| 250 |
+
255,
|
| 251 |
+
106,
|
| 252 |
+
107,
|
| 253 |
+
108,
|
| 254 |
+
109,
|
| 255 |
+
110,
|
| 256 |
+
111,
|
| 257 |
+
112,
|
| 258 |
+
113,
|
| 259 |
+
114,
|
| 260 |
+
115,
|
| 261 |
+
116,
|
| 262 |
+
117,
|
| 263 |
+
118,
|
| 264 |
+
119,
|
| 265 |
+
120,
|
| 266 |
+
121,
|
| 267 |
+
122,
|
| 268 |
+
123,
|
| 269 |
+
124,
|
| 270 |
+
125,
|
| 271 |
+
126,
|
| 272 |
+
127,
|
| 273 |
+
128,
|
| 274 |
+
129,
|
| 275 |
+
130,
|
| 276 |
+
131,
|
| 277 |
+
132,
|
| 278 |
+
133,
|
| 279 |
+
134,
|
| 280 |
+
135,
|
| 281 |
+
136,
|
| 282 |
+
137,
|
| 283 |
+
138,
|
| 284 |
+
139,
|
| 285 |
+
140,
|
| 286 |
+
141,
|
| 287 |
+
142,
|
| 288 |
+
143,
|
| 289 |
+
144,
|
| 290 |
+
145,
|
| 291 |
+
146,
|
| 292 |
+
147,
|
| 293 |
+
148,
|
| 294 |
+
149,
|
| 295 |
+
150,
|
| 296 |
+
151,
|
| 297 |
+
152,
|
| 298 |
+
153,
|
| 299 |
+
154,
|
| 300 |
+
155,
|
| 301 |
+
156,
|
| 302 |
+
157,
|
| 303 |
+
158,
|
| 304 |
+
159,
|
| 305 |
+
160,
|
| 306 |
+
161,
|
| 307 |
+
162,
|
| 308 |
+
163,
|
| 309 |
+
164,
|
| 310 |
+
165,
|
| 311 |
+
166,
|
| 312 |
+
167,
|
| 313 |
+
168,
|
| 314 |
+
169,
|
| 315 |
+
170,
|
| 316 |
+
171,
|
| 317 |
+
172,
|
| 318 |
+
173,
|
| 319 |
+
174,
|
| 320 |
+
175,
|
| 321 |
+
176,
|
| 322 |
+
177,
|
| 323 |
+
178,
|
| 324 |
+
179,
|
| 325 |
+
180,
|
| 326 |
+
181,
|
| 327 |
+
182,
|
| 328 |
+
183,
|
| 329 |
+
184,
|
| 330 |
+
185,
|
| 331 |
+
186,
|
| 332 |
+
187,
|
| 333 |
+
248066,
|
| 334 |
+
248067,
|
| 335 |
+
248068,
|
| 336 |
+
248069,
|
| 337 |
+
248046,
|
| 338 |
+
248045,
|
| 339 |
+
846,
|
| 340 |
+
74455,
|
| 341 |
+
271
|
| 342 |
+
],
|
| 343 |
+
"think_logit_damped": true
|
| 344 |
+
}
|
config.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen3_5ForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"output_hidden_states": false,
|
| 6 |
+
"return_dict": true,
|
| 7 |
+
"dtype": "bfloat16",
|
| 8 |
+
"chunk_size_feed_forward": 0,
|
| 9 |
+
"is_encoder_decoder": false,
|
| 10 |
+
"id2label": {
|
| 11 |
+
"0": "LABEL_0",
|
| 12 |
+
"1": "LABEL_1"
|
| 13 |
+
},
|
| 14 |
+
"label2id": {
|
| 15 |
+
"LABEL_0": 0,
|
| 16 |
+
"LABEL_1": 1
|
| 17 |
+
},
|
| 18 |
+
"problem_type": null,
|
| 19 |
+
"vocab_size": 478656,
|
| 20 |
+
"hidden_size": 5632,
|
| 21 |
+
"intermediate_size": 12288,
|
| 22 |
+
"num_hidden_layers": 43,
|
| 23 |
+
"num_attention_heads": 16,
|
| 24 |
+
"num_key_value_heads": 4,
|
| 25 |
+
"hidden_act": "silu",
|
| 26 |
+
"max_position_embeddings": 262144,
|
| 27 |
+
"initializer_range": 0.02,
|
| 28 |
+
"rms_norm_eps": 1e-06,
|
| 29 |
+
"use_cache": true,
|
| 30 |
+
"tie_word_embeddings": false,
|
| 31 |
+
"rope_parameters": {
|
| 32 |
+
"mrope_interleaved": true,
|
| 33 |
+
"mrope_section": [
|
| 34 |
+
11,
|
| 35 |
+
11,
|
| 36 |
+
10
|
| 37 |
+
],
|
| 38 |
+
"rope_type": "default",
|
| 39 |
+
"rope_theta": 10000000,
|
| 40 |
+
"partial_rotary_factor": 0.25
|
| 41 |
+
},
|
| 42 |
+
"attention_bias": false,
|
| 43 |
+
"attention_dropout": 0.0,
|
| 44 |
+
"head_dim": 256,
|
| 45 |
+
"linear_conv_kernel_dim": 4,
|
| 46 |
+
"linear_key_head_dim": 128,
|
| 47 |
+
"linear_value_head_dim": 128,
|
| 48 |
+
"linear_num_key_heads": 16,
|
| 49 |
+
"linear_num_value_heads": 32,
|
| 50 |
+
"layer_types": [
|
| 51 |
+
"linear_attention",
|
| 52 |
+
"linear_attention",
|
| 53 |
+
"linear_attention",
|
| 54 |
+
"linear_attention",
|
| 55 |
+
"linear_attention",
|
| 56 |
+
"linear_attention",
|
| 57 |
+
"linear_attention",
|
| 58 |
+
"linear_attention",
|
| 59 |
+
"linear_attention",
|
| 60 |
+
"linear_attention",
|
| 61 |
+
"linear_attention",
|
| 62 |
+
"linear_attention",
|
| 63 |
+
"linear_attention",
|
| 64 |
+
"linear_attention",
|
| 65 |
+
"full_attention",
|
| 66 |
+
"linear_attention",
|
| 67 |
+
"linear_attention",
|
| 68 |
+
"linear_attention",
|
| 69 |
+
"full_attention",
|
| 70 |
+
"linear_attention",
|
| 71 |
+
"linear_attention",
|
| 72 |
+
"linear_attention",
|
| 73 |
+
"full_attention",
|
| 74 |
+
"linear_attention",
|
| 75 |
+
"linear_attention",
|
| 76 |
+
"linear_attention",
|
| 77 |
+
"full_attention",
|
| 78 |
+
"linear_attention",
|
| 79 |
+
"linear_attention",
|
| 80 |
+
"linear_attention",
|
| 81 |
+
"full_attention",
|
| 82 |
+
"linear_attention",
|
| 83 |
+
"linear_attention",
|
| 84 |
+
"linear_attention",
|
| 85 |
+
"full_attention",
|
| 86 |
+
"linear_attention",
|
| 87 |
+
"linear_attention",
|
| 88 |
+
"linear_attention",
|
| 89 |
+
"full_attention",
|
| 90 |
+
"linear_attention",
|
| 91 |
+
"linear_attention",
|
| 92 |
+
"linear_attention",
|
| 93 |
+
"full_attention"
|
| 94 |
+
],
|
| 95 |
+
"pad_token_id": null,
|
| 96 |
+
"bos_token_id": null,
|
| 97 |
+
"eos_token_id": 248044,
|
| 98 |
+
"attn_output_gate": true,
|
| 99 |
+
"mlp_only_layers": [],
|
| 100 |
+
"model_type": "qwen3_5_text",
|
| 101 |
+
"mtp_num_hidden_layers": 0,
|
| 102 |
+
"mtp_use_dedicated_embeddings": false,
|
| 103 |
+
"mamba_ssm_dtype": "float32",
|
| 104 |
+
"output_attentions": false
|
| 105 |
+
}
|
firmware/build.sh
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Build the Carbon Graphing Calculator RV32IM+Zba+Bignum firmware (run in WSL):
|
| 3 |
+
# bash carbon/firmware/build.sh [OUT_DIR]
|
| 4 |
+
# Produces:
|
| 5 |
+
# OUT_DIR/graphcalc.elf (for compiled Qwen3.5 RV32 chip layers & chipformer.rv.ref.Machine)
|
| 6 |
+
# OUT_DIR/graphcalc-qemu.elf (for standalone qemu-riscv32 verification via stdin)
|
| 7 |
+
set -euo pipefail
|
| 8 |
+
HERE=$(cd "$(dirname "$0")" && pwd)
|
| 9 |
+
OUT=$(realpath "${1:-$HERE}")
|
| 10 |
+
QWENPUTER=${QWENPUTER:-$HERE/../../../qwenputer}
|
| 11 |
+
ENV=$QWENPUTER/experimental/riscv/chipformer/rv/env
|
| 12 |
+
CALC_ENV=$ENV/calc
|
| 13 |
+
GCC=riscv64-unknown-elf-gcc
|
| 14 |
+
MARCH=rv32im_zba
|
| 15 |
+
PICO=/usr/lib/picolibc/riscv64-unknown-elf
|
| 16 |
+
LIBC=$PICO/lib/release/rv32im/ilp32/libc.a
|
| 17 |
+
[ -f "$LIBC" ] || LIBC=$(find /usr/lib/picolibc -path '*rv32im/ilp32/libc.a' | head -n 1)
|
| 18 |
+
LIBGCC=$($GCC -march=rv32im -mabi=ilp32 -print-libgcc-file-name)
|
| 19 |
+
|
| 20 |
+
B=$(mktemp -d)
|
| 21 |
+
python3 "$HERE/tables.py" "$B/graphcalc_tables.h" "$B/sled.S"
|
| 22 |
+
mkdir -p "$OUT"
|
| 23 |
+
|
| 24 |
+
for map in rom qemu; do
|
| 25 |
+
name=$([ $map = rom ] && echo graphcalc || echo graphcalc-qemu)
|
| 26 |
+
def=$([ $map = rom ] && echo "" || echo "-DQEMU")
|
| 27 |
+
sled=$([ $map = rom ] && echo "$B/sled.S" || echo "")
|
| 28 |
+
$GCC -march=$MARCH -mabi=ilp32 ${CFLAGS:--O2} -fno-tree-loop-distribute-patterns -Wall -Wextra $def \
|
| 29 |
+
-isystem "$PICO/include" -I"$ENV" -I"$CALC_ENV" -I"$B" \
|
| 30 |
+
-nostdlib -nostartfiles -static -T"$ENV/$map.ld" -o "$OUT/$name.elf" \
|
| 31 |
+
"$ENV/crt0.S" "$HERE/graphcalc.c" "$ENV/sys.c" $sled \
|
| 32 |
+
-Wl,--start-group "$LIBC" "$LIBGCC" -Wl,--end-group
|
| 33 |
+
done
|
| 34 |
+
|
| 35 |
+
riscv64-unknown-elf-size -A "$OUT/graphcalc.elf" | grep -E '^\.(text|rodata|data|bss)'
|
| 36 |
+
rm -rf "$B"
|
firmware/graphcalc.c
ADDED
|
@@ -0,0 +1,2210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Carbon In-Weights Graphing & Symbolic/Numeric Calculator Firmware (RV32IM + Zba + Bignum).
|
| 2 |
+
// Executes directly inside compiled Qwen3.5 Gated-DeltaNet + SwiGLU layers (layers[0..10]).
|
| 3 |
+
// Strictly integer & fixed-point (Q32.32 + 128-digit hardware bignum register file) — zero FPU.
|
| 4 |
+
|
| 5 |
+
#include <stdint.h>
|
| 6 |
+
#include <stddef.h>
|
| 7 |
+
#include "rt.h"
|
| 8 |
+
#include "graphcalc_tables.h"
|
| 9 |
+
|
| 10 |
+
#ifdef QEMU
|
| 11 |
+
// Software emulation of chipformer's custom-0/custom-1 128-digit bignum unit for qemu-riscv32.
|
| 12 |
+
#define BN_OPS(n, off, m, d, s, ovw) \
|
| 13 |
+
((uint32_t)(d) | (uint32_t)(s) << 3 | (uint32_t)(ovw) << 6 | (uint32_t)(n) << 7 | \
|
| 14 |
+
(uint32_t)(off) << 13 | (uint32_t)(m) << 20)
|
| 15 |
+
#define BN_OFF(off) ((uint32_t)(off) << 13)
|
| 16 |
+
#define BN_M(m) ((uint32_t)(m) << 20)
|
| 17 |
+
|
| 18 |
+
static uint8_t sw_bn[8][128];
|
| 19 |
+
|
| 20 |
+
static uint32_t bn_mac(uint32_t ops) {
|
| 21 |
+
uint32_t dd = ops & 7, ds = (ops >> 3) & 7, ovw = (ops >> 6) & 1;
|
| 22 |
+
uint32_t n = (ops >> 7) & 63, off = (ops >> 13) & 127, m = (ops >> 20) & 15;
|
| 23 |
+
if (m > 9) m = 9;
|
| 24 |
+
uint32_t c = 0;
|
| 25 |
+
for (uint32_t i = 0; i < n; i++) {
|
| 26 |
+
uint32_t r = i + off;
|
| 27 |
+
uint32_t d = r < 128 ? sw_bn[dd][r] : 0;
|
| 28 |
+
uint32_t v = (ovw ? 0 : d) + sw_bn[ds][i] * m + c;
|
| 29 |
+
c = v / 10;
|
| 30 |
+
if (r < 128) sw_bn[dd][r] = (uint8_t)(v % 10);
|
| 31 |
+
}
|
| 32 |
+
return c;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
static uint32_t bn_msb(uint32_t ops) {
|
| 36 |
+
uint32_t dd = ops & 7, ds = (ops >> 3) & 7;
|
| 37 |
+
uint32_t n = (ops >> 7) & 63, off = (ops >> 13) & 127, m = (ops >> 20) & 15;
|
| 38 |
+
if (m > 9) m = 9;
|
| 39 |
+
int32_t c = 0;
|
| 40 |
+
for (uint32_t i = 0; i < n; i++) {
|
| 41 |
+
uint32_t r = i + off;
|
| 42 |
+
int32_t d = r < 128 ? (int32_t)sw_bn[dd][r] : 0;
|
| 43 |
+
int32_t v = d - (int32_t)(sw_bn[ds][i] * m) - c;
|
| 44 |
+
int32_t rem = ((v % 10) + 10) % 10;
|
| 45 |
+
c = (rem - v) / 10;
|
| 46 |
+
if (r < 128) sw_bn[dd][r] = (uint8_t)rem;
|
| 47 |
+
}
|
| 48 |
+
return (uint32_t)c;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
static void bn_ld(const char *end, uint32_t ops) {
|
| 52 |
+
uint32_t dd = ops & 7, n = (ops >> 7) & 63, off = (ops >> 13) & 127;
|
| 53 |
+
for (uint32_t i = 0; i < n; i++) {
|
| 54 |
+
uint32_t r = i + off;
|
| 55 |
+
uint32_t v = (uint32_t)((unsigned char)end[-1 - (int)i]) & 15u;
|
| 56 |
+
if (v > 9) v = 9;
|
| 57 |
+
if (r < 128) sw_bn[dd][r] = (uint8_t)v;
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
static uint32_t bn_get(uint32_t ops) {
|
| 62 |
+
uint32_t dd = ops & 7, off = (ops >> 13) & 127;
|
| 63 |
+
return sw_bn[dd][off];
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
static uint32_t bn_scan(const char *s) {
|
| 67 |
+
uint32_t n = 0;
|
| 68 |
+
while (n < 63 && (unsigned char)s[n] >= '0' && (unsigned char)s[n] <= '9') n++;
|
| 69 |
+
return n;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
static void bn_print(int r, int len) {
|
| 73 |
+
for (int i = len - 1; i >= 0; i--) rv_putchar((char)('0' + sw_bn[r][i]));
|
| 74 |
+
}
|
| 75 |
+
#else
|
| 76 |
+
#include "bn.h"
|
| 77 |
+
#endif
|
| 78 |
+
|
| 79 |
+
// ============================================================================
|
| 80 |
+
// Basic Output & String Helpers (no libc printf bloat)
|
| 81 |
+
// ============================================================================
|
| 82 |
+
|
| 83 |
+
static void puts_raw(const char *s) {
|
| 84 |
+
while (*s) rv_putchar(*s++);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
static void putc_rep(char c, int n) {
|
| 88 |
+
for (int i = 0; i < n; i++) rv_putchar(c);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
static int slen(const char *s) {
|
| 92 |
+
int n = 0;
|
| 93 |
+
while (s[n]) n++;
|
| 94 |
+
return n;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
static int streq(const char *a, const char *b) {
|
| 98 |
+
while (*a && *b && *a == *b) { a++; b++; }
|
| 99 |
+
return *a == 0 && *b == 0;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
static int streq_ci(const char *a, const char *b) {
|
| 103 |
+
while (*a && *b) {
|
| 104 |
+
char ca = *a++, cb = *b++;
|
| 105 |
+
if (ca >= 'A' && ca <= 'Z') ca = (char)(ca + 32);
|
| 106 |
+
if (cb >= 'A' && cb <= 'Z') cb = (char)(cb + 32);
|
| 107 |
+
if (ca != cb) return 0;
|
| 108 |
+
}
|
| 109 |
+
return *a == 0 && *b == 0;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
static int starts_with_ci(const char *s, const char *p) {
|
| 113 |
+
while (*p) {
|
| 114 |
+
char cs = *s++, cp = *p++;
|
| 115 |
+
if (cs >= 'A' && cs <= 'Z') cs = (char)(cs + 32);
|
| 116 |
+
if (cp >= 'A' && cp <= 'Z') cp = (char)(cp + 32);
|
| 117 |
+
if (cs != cp) return 0;
|
| 118 |
+
}
|
| 119 |
+
return 1;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
static int is_space(char c) {
|
| 123 |
+
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
static int is_alpha(char c) {
|
| 127 |
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
static int is_digit(char c) {
|
| 131 |
+
return c >= '0' && c <= '9';
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
static void copy_trim(char *dst, int cap, const char *src, int len) {
|
| 135 |
+
int a = 0, b = len;
|
| 136 |
+
while (a < b && is_space(src[a])) a++;
|
| 137 |
+
while (b > a && is_space(src[b - 1])) b--;
|
| 138 |
+
if (b - a >= 2 && ((src[a] == '"' && src[b - 1] == '"') ||
|
| 139 |
+
(src[a] == '\'' && src[b - 1] == '\'') ||
|
| 140 |
+
(src[a] == '$' && src[b - 1] == '$'))) {
|
| 141 |
+
a++;
|
| 142 |
+
b--;
|
| 143 |
+
}
|
| 144 |
+
int o = 0;
|
| 145 |
+
for (int i = a; i < b && o < cap - 1; i++) {
|
| 146 |
+
dst[o++] = src[i] == '\n' || src[i] == '\r' ? ' ' : src[i];
|
| 147 |
+
}
|
| 148 |
+
dst[o] = 0;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
static void append_str(char *buf, int cap, int *pos, const char *s) {
|
| 152 |
+
while (*s && *pos < cap - 1) buf[(*pos)++] = *s++;
|
| 153 |
+
buf[*pos] = 0;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
static void append_char(char *buf, int cap, int *pos, char c) {
|
| 157 |
+
if (*pos < cap - 1) {
|
| 158 |
+
buf[(*pos)++] = c;
|
| 159 |
+
buf[*pos] = 0;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// ============================================================================
|
| 164 |
+
// Engine A: 62-Digit Hardware Bignum + Exact Reduced Rationals (bn.h)
|
| 165 |
+
// ============================================================================
|
| 166 |
+
|
| 167 |
+
#define MAXD 62
|
| 168 |
+
#define ONE_REG 7
|
| 169 |
+
|
| 170 |
+
typedef uint32_t num;
|
| 171 |
+
#define REG(a) ((a) & 7u)
|
| 172 |
+
#define NEG(a) (((a) >> 8) & 1u)
|
| 173 |
+
#define LEN(a) ((int)((a) >> 16))
|
| 174 |
+
#define NUM(r, neg, len) ((uint32_t)(r) | ((uint32_t)(neg) << 8) | ((uint32_t)(len) << 16))
|
| 175 |
+
|
| 176 |
+
static uint32_t bn_dirty[8];
|
| 177 |
+
static uint32_t bn_used;
|
| 178 |
+
static int bn_err;
|
| 179 |
+
static char one_ch = '1'; // MUST stay in RAM (.data), NOT const (.rodata)!
|
| 180 |
+
|
| 181 |
+
static char bn_steps[512];
|
| 182 |
+
static int bn_step_pos;
|
| 183 |
+
static int bn_step_cnt;
|
| 184 |
+
|
| 185 |
+
static int bn_dig(num a, int i) { return (int)bn_get(BN_OPS(0, i, 0, REG(a), 0, 0)); }
|
| 186 |
+
|
| 187 |
+
static void bn_wrote(num a, int n) {
|
| 188 |
+
if ((uint32_t)n > bn_dirty[REG(a)]) bn_dirty[REG(a)] = (uint32_t)n;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
static num bn_alloc(int len) {
|
| 192 |
+
for (int r = 0; r < ONE_REG; r++) {
|
| 193 |
+
if (!(bn_used >> r & 1u)) {
|
| 194 |
+
bn_used |= 1u << r;
|
| 195 |
+
for (uint32_t o = 0; o < bn_dirty[r]; o += 63) {
|
| 196 |
+
uint32_t chunk = bn_dirty[r] - o < 63 ? bn_dirty[r] - o : 63;
|
| 197 |
+
bn_mac(BN_OPS(chunk, o, 0, r, r, 1));
|
| 198 |
+
}
|
| 199 |
+
bn_dirty[r] = 0;
|
| 200 |
+
return NUM(r, 0, len);
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
bn_err = 2;
|
| 204 |
+
return NUM(0, 0, len);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
static void bn_release(num a) { bn_used &= ~(1u << REG(a)); }
|
| 208 |
+
|
| 209 |
+
static int bn_is_zero(num a) { return LEN(a) == 1 && !bn_dig(a, 0); }
|
| 210 |
+
|
| 211 |
+
static num bn_trim(num a) {
|
| 212 |
+
int n = LEN(a);
|
| 213 |
+
while (n > 1 && !bn_dig(a, n - 1)) n--;
|
| 214 |
+
a = (a & 0xFFFFu) | ((uint32_t)n << 16);
|
| 215 |
+
return (n == 1 && !bn_dig(a, 0)) ? (a & ~0x100u) : a;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
static int bn_cmp_mag(num a, num b) {
|
| 219 |
+
if (LEN(a) != LEN(b)) return LEN(a) < LEN(b) ? -1 : 1;
|
| 220 |
+
for (int i = LEN(a) - 1; i >= 0; i--) {
|
| 221 |
+
int x = bn_dig(a, i), y = bn_dig(b, i);
|
| 222 |
+
if (x != y) return x < y ? -1 : 1;
|
| 223 |
+
}
|
| 224 |
+
return 0;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
static num bn_add_mag(num a, num b) {
|
| 228 |
+
int n = (LEN(a) > LEN(b) ? LEN(a) : LEN(b)) + 1;
|
| 229 |
+
if (n > MAXD) bn_err = 1;
|
| 230 |
+
bn_mac(BN_OPS(n, 0, 1, REG(a), REG(b), 0));
|
| 231 |
+
bn_wrote(a, n);
|
| 232 |
+
if (!bn_dig(a, n - 1)) n--;
|
| 233 |
+
return (a & 0xFFFFu) | ((uint32_t)n << 16);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
static num bn_sub_mag(num a, num b) {
|
| 237 |
+
bn_msb(BN_OPS(LEN(a), 0, 1, REG(a), REG(b), 0));
|
| 238 |
+
return bn_trim(a);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
__attribute__((noinline)) static num bn_add(num a, num b) {
|
| 242 |
+
num s;
|
| 243 |
+
if (NEG(a) == NEG(b)) {
|
| 244 |
+
s = bn_add_mag(a, b);
|
| 245 |
+
bn_release(b);
|
| 246 |
+
} else if (bn_cmp_mag(a, b) >= 0) {
|
| 247 |
+
s = bn_sub_mag(a, b);
|
| 248 |
+
bn_release(b);
|
| 249 |
+
} else {
|
| 250 |
+
s = bn_sub_mag(b, a);
|
| 251 |
+
bn_release(a);
|
| 252 |
+
}
|
| 253 |
+
return s;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
__attribute__((noinline)) static num bn_mul(num a, num b) {
|
| 257 |
+
if (LEN(a) < LEN(b)) { num t = a; a = b; b = t; }
|
| 258 |
+
int la = LEN(a), lb = LEN(b);
|
| 259 |
+
num c = bn_alloc(la + lb);
|
| 260 |
+
if (la + lb > MAXD) {
|
| 261 |
+
bn_err = 1;
|
| 262 |
+
return c;
|
| 263 |
+
}
|
| 264 |
+
uint32_t base = BN_OPS(la + 1, 0, 0, REG(c), REG(a), 0);
|
| 265 |
+
for (int j = 0; j < lb; j++) {
|
| 266 |
+
uint32_t d = (uint32_t)bn_dig(b, j);
|
| 267 |
+
if (d) bn_mac(base | BN_OFF(j) | BN_M(d));
|
| 268 |
+
}
|
| 269 |
+
bn_wrote(c, la + lb);
|
| 270 |
+
return bn_trim(c | ((NEG(a) ^ NEG(b)) << 8));
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
typedef struct { num q, r; } bn_qr;
|
| 274 |
+
|
| 275 |
+
__attribute__((noinline)) static bn_qr bn_divmod(num a, num b) {
|
| 276 |
+
if (bn_is_zero(b)) bn_err = 1;
|
| 277 |
+
if (bn_err || bn_cmp_mag(a, b) < 0) return (bn_qr){bn_alloc(1), a & ~0x100u};
|
| 278 |
+
int la = LEN(a), lb = LEN(b), ra = (int)REG(a);
|
| 279 |
+
num q = bn_alloc(la - lb + 1);
|
| 280 |
+
int b1 = bn_dig(b, lb - 1), den = lb >= 2 ? b1 * 10 + bn_dig(b, lb - 2) : b1;
|
| 281 |
+
uint32_t msb = BN_OPS(lb + 1, 0, 0, ra, REG(b), 0), mac = BN_OPS(lb + 1, 0, 1, ra, REG(b), 0);
|
| 282 |
+
uint32_t setq = BN_OPS(1, 0, 0, REG(q), ONE_REG, 1);
|
| 283 |
+
for (int t = la - lb; t >= 0; t--) {
|
| 284 |
+
int top = t + lb;
|
| 285 |
+
int w = bn_dig(a, top) * 10 + bn_dig(a, top - 1);
|
| 286 |
+
if (lb >= 2) w = w * 10 + bn_dig(a, top - 2);
|
| 287 |
+
int qd = (lb >= 3 ? w + 1 : w) / den;
|
| 288 |
+
if (qd > 9) qd = 9;
|
| 289 |
+
if (qd) {
|
| 290 |
+
uint32_t o = BN_OFF(t);
|
| 291 |
+
if (bn_msb(msb | o | BN_M(qd)))
|
| 292 |
+
do qd--;
|
| 293 |
+
while (!bn_mac(mac | o) && qd > 0);
|
| 294 |
+
if (qd) bn_mac(setq | o | BN_M(qd));
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
bn_wrote(q, la - lb + 1);
|
| 298 |
+
return (bn_qr){bn_trim(q), bn_trim(NUM(ra, 0, lb))};
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
__attribute__((noinline)) static num bn_divide(num a, num b, int want_mod) {
|
| 302 |
+
uint32_t sa = NEG(a), sb = NEG(b);
|
| 303 |
+
bn_qr d = bn_divmod(a, b);
|
| 304 |
+
if (sa != sb && !bn_is_zero(d.r)) {
|
| 305 |
+
d.q = bn_add_mag(d.q, NUM(ONE_REG, 0, 1));
|
| 306 |
+
num r2 = bn_sub_mag(b & ~0x100u, d.r);
|
| 307 |
+
bn_release(d.r);
|
| 308 |
+
d.r = r2;
|
| 309 |
+
} else {
|
| 310 |
+
bn_release(b);
|
| 311 |
+
}
|
| 312 |
+
bn_release(want_mod ? d.q : d.r);
|
| 313 |
+
num out = want_mod ? d.r : d.q;
|
| 314 |
+
out = (out & ~0x100u) | ((want_mod ? sb : (sa ^ sb)) << 8);
|
| 315 |
+
return bn_is_zero(out) ? (out & ~0x100u) : out;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
__attribute__((noinline)) static num bn_gcd(num a, num b) {
|
| 319 |
+
a &= ~0x100u;
|
| 320 |
+
b &= ~0x100u;
|
| 321 |
+
while (!bn_err && !bn_is_zero(b)) {
|
| 322 |
+
bn_qr d = bn_divmod(a, b);
|
| 323 |
+
bn_release(d.q);
|
| 324 |
+
a = b;
|
| 325 |
+
b = d.r;
|
| 326 |
+
}
|
| 327 |
+
bn_release(b);
|
| 328 |
+
return a;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
__attribute__((noinline)) static num bn_power(num a, num e) {
|
| 332 |
+
int x = 0;
|
| 333 |
+
if (NEG(e) || LEN(e) > 3) bn_err = 1;
|
| 334 |
+
for (int i = LEN(e) - 1; i >= 0 && !bn_err; i--) x = x * 10 + bn_dig(e, i);
|
| 335 |
+
bn_release(e);
|
| 336 |
+
num res = bn_alloc(1);
|
| 337 |
+
bn_mac(BN_OPS(1, 0, 1, REG(res), ONE_REG, 1));
|
| 338 |
+
bn_wrote(res, 1);
|
| 339 |
+
while (x && !bn_err) {
|
| 340 |
+
if (x & 1) {
|
| 341 |
+
num t = bn_mul(res, a);
|
| 342 |
+
bn_release(res);
|
| 343 |
+
res = t;
|
| 344 |
+
}
|
| 345 |
+
x >>= 1;
|
| 346 |
+
if (x) {
|
| 347 |
+
num t = bn_mul(a, a);
|
| 348 |
+
bn_release(a);
|
| 349 |
+
a = t;
|
| 350 |
+
}
|
| 351 |
+
}
|
| 352 |
+
bn_release(a);
|
| 353 |
+
return res;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
static num bn_vals[16];
|
| 357 |
+
static num bn_vden[16];
|
| 358 |
+
static int bn_hasden[16];
|
| 359 |
+
static int bn_vsc[16];
|
| 360 |
+
static char bn_ops[16];
|
| 361 |
+
|
| 362 |
+
static int bn_prec(int op) {
|
| 363 |
+
return op == '^' ? 4 : op == 'n' ? 3 : (op == '*' || op == 'f' || op == 'd' || op == '%') ? 2 : 1;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
static num bn_copy_of(num a) {
|
| 367 |
+
num c = bn_alloc(LEN(a));
|
| 368 |
+
bn_mac(BN_OPS(LEN(a), 0, 1, REG(c), REG(a), 1));
|
| 369 |
+
bn_wrote(c, LEN(a));
|
| 370 |
+
return NUM(REG(c), NEG(a), LEN(a));
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
__attribute__((noinline)) static void bn_reduce(int i) {
|
| 374 |
+
num n = bn_vals[i], d = bn_vden[i];
|
| 375 |
+
bn_hasden[i] = 0;
|
| 376 |
+
if (bn_err) return;
|
| 377 |
+
if (bn_is_zero(n)) {
|
| 378 |
+
bn_release(d);
|
| 379 |
+
return;
|
| 380 |
+
}
|
| 381 |
+
uint32_t neg = NEG(n);
|
| 382 |
+
num g = bn_gcd(bn_copy_of(n), bn_copy_of(d));
|
| 383 |
+
bn_qr x = bn_divmod(n & ~0x100u, g);
|
| 384 |
+
bn_release(x.r);
|
| 385 |
+
bn_qr y = bn_divmod(d, g);
|
| 386 |
+
bn_release(y.r);
|
| 387 |
+
bn_release(g);
|
| 388 |
+
bn_vals[i] = x.q | (neg << 8);
|
| 389 |
+
if (LEN(y.q) == 1 && bn_dig(y.q, 0) == 1) {
|
| 390 |
+
bn_release(y.q);
|
| 391 |
+
} else {
|
| 392 |
+
bn_vden[i] = y.q;
|
| 393 |
+
bn_hasden[i] = 1;
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
__attribute__((noinline)) static void bn_frac_op(int op, int i, num a, num da, int fa, num b, num db, int fb) {
|
| 398 |
+
num n, d;
|
| 399 |
+
if (op == '*') {
|
| 400 |
+
n = bn_mul(a, b);
|
| 401 |
+
bn_release(a);
|
| 402 |
+
bn_release(b);
|
| 403 |
+
if (fa && fb) {
|
| 404 |
+
d = bn_mul(da, db);
|
| 405 |
+
bn_release(da);
|
| 406 |
+
bn_release(db);
|
| 407 |
+
} else {
|
| 408 |
+
d = fa ? da : db;
|
| 409 |
+
}
|
| 410 |
+
} else if (op == 'd') {
|
| 411 |
+
if (bn_is_zero(b)) bn_err = 1;
|
| 412 |
+
if (fb) { n = bn_mul(a, db); bn_release(a); bn_release(db); }
|
| 413 |
+
else n = a;
|
| 414 |
+
if (fa) { d = bn_mul(da, b); bn_release(da); bn_release(b); }
|
| 415 |
+
else d = b;
|
| 416 |
+
if (NEG(d)) {
|
| 417 |
+
d &= ~0x100u;
|
| 418 |
+
if (!bn_is_zero(n)) n ^= 0x100u;
|
| 419 |
+
}
|
| 420 |
+
} else {
|
| 421 |
+
num t1 = a, t2 = b;
|
| 422 |
+
if (fb) { t1 = bn_mul(a, db); bn_release(a); }
|
| 423 |
+
if (fa) { t2 = bn_mul(b, da); bn_release(b); }
|
| 424 |
+
if (fa && fb) { d = bn_mul(da, db); bn_release(da); bn_release(db); }
|
| 425 |
+
else d = fa ? da : db;
|
| 426 |
+
n = bn_add(t1, (op == '-' && !bn_is_zero(t2)) ? (t2 ^ 0x100u) : t2);
|
| 427 |
+
}
|
| 428 |
+
bn_vals[i] = n;
|
| 429 |
+
bn_vden[i] = d;
|
| 430 |
+
bn_reduce(i);
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
static num bn_shift(num a, int k) {
|
| 434 |
+
int n = LEN(a) + k;
|
| 435 |
+
if (n > MAXD) { bn_err = 1; return a; }
|
| 436 |
+
num c = bn_alloc(n);
|
| 437 |
+
bn_mac(BN_OPS(LEN(a), k, 1, REG(c), REG(a), 1));
|
| 438 |
+
bn_wrote(c, n);
|
| 439 |
+
bn_release(a);
|
| 440 |
+
return NUM(REG(c), NEG(a), n);
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
static void bn_fmt_slot(int idx, char *buf, int cap) {
|
| 444 |
+
int p = 0;
|
| 445 |
+
num v = bn_vals[idx];
|
| 446 |
+
int sc = bn_vsc[idx], n = LEN(v);
|
| 447 |
+
if (NEG(v)) append_char(buf, cap, &p, '-');
|
| 448 |
+
if (!sc) {
|
| 449 |
+
for (int i = n - 1; i >= 0; i--) append_char(buf, cap, &p, (char)('0' + bn_dig(v, i)));
|
| 450 |
+
} else {
|
| 451 |
+
int z = 0;
|
| 452 |
+
while (z < sc && !bn_dig(v, z)) z++;
|
| 453 |
+
if (n <= sc) append_char(buf, cap, &p, '0');
|
| 454 |
+
for (int i = n > sc ? n - 1 : sc - 1; i >= z; i--) {
|
| 455 |
+
if (i == sc - 1) append_char(buf, cap, &p, '.');
|
| 456 |
+
append_char(buf, cap, &p, (char)('0' + (i < n ? bn_dig(v, i) : 0)));
|
| 457 |
+
}
|
| 458 |
+
}
|
| 459 |
+
if (bn_hasden[idx]) {
|
| 460 |
+
num dv = bn_vden[idx];
|
| 461 |
+
append_char(buf, cap, &p, '/');
|
| 462 |
+
for (int i = LEN(dv) - 1; i >= 0; i--) append_char(buf, cap, &p, (char)('0' + bn_dig(dv, i)));
|
| 463 |
+
}
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
static int bn_apply(int op, int nv) {
|
| 467 |
+
if (op == 'n') {
|
| 468 |
+
num v = bn_vals[nv - 1];
|
| 469 |
+
bn_vals[nv - 1] = bn_is_zero(v) ? v : (v ^ 0x100u);
|
| 470 |
+
return nv;
|
| 471 |
+
}
|
| 472 |
+
if (nv < 2) { bn_err = 1; return nv; }
|
| 473 |
+
|
| 474 |
+
char sa_str[72], sb_str[72], sr_str[72];
|
| 475 |
+
int can_log = (!bn_err && bn_step_cnt < 8 && LEN(bn_vals[nv - 2]) <= 30 && LEN(bn_vals[nv - 1]) <= 30);
|
| 476 |
+
if (can_log) {
|
| 477 |
+
bn_fmt_slot(nv - 2, sa_str, sizeof sa_str);
|
| 478 |
+
bn_fmt_slot(nv - 1, sb_str, sizeof sb_str);
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
int sb = bn_vsc[nv - 1], sa = bn_vsc[nv - 2];
|
| 482 |
+
int fb = bn_hasden[nv - 1], fa = bn_hasden[nv - 2];
|
| 483 |
+
num b = bn_vals[--nv], a = bn_vals[nv - 1], r;
|
| 484 |
+
int rational = op == 'd' || ((fa || fb) && (op == '+' || op == '-' || op == '*'));
|
| 485 |
+
if ((fa || fb) && !rational) bn_err = 1;
|
| 486 |
+
if (rational && (sa || sb)) bn_err = 1;
|
| 487 |
+
if (bn_err) {
|
| 488 |
+
r = a;
|
| 489 |
+
bn_hasden[nv - 1] = 0;
|
| 490 |
+
bn_vals[nv - 1] = r;
|
| 491 |
+
bn_vsc[nv - 1] = sa;
|
| 492 |
+
} else if (rational) {
|
| 493 |
+
bn_frac_op(op, nv - 1, a, bn_vden[nv - 1], fa, b, bn_vden[nv], fb);
|
| 494 |
+
bn_vsc[nv - 1] = 0;
|
| 495 |
+
} else {
|
| 496 |
+
if (op == '+' || op == '-') {
|
| 497 |
+
if (sa < sb) { a = bn_shift(a, sb - sa); sa = sb; }
|
| 498 |
+
if (sb < sa) b = bn_shift(b, sa - sb);
|
| 499 |
+
r = bn_add(a, (op == '-' && !bn_is_zero(b)) ? (b ^ 0x100u) : b);
|
| 500 |
+
} else if (op == '*') {
|
| 501 |
+
r = bn_mul(a, b);
|
| 502 |
+
bn_release(a);
|
| 503 |
+
bn_release(b);
|
| 504 |
+
sa += sb;
|
| 505 |
+
} else if (sa || sb) {
|
| 506 |
+
bn_err = 1;
|
| 507 |
+
r = a;
|
| 508 |
+
} else if (op == '^') {
|
| 509 |
+
r = bn_power(a, b);
|
| 510 |
+
} else if (op == 'g') {
|
| 511 |
+
r = bn_gcd(a, b);
|
| 512 |
+
} else {
|
| 513 |
+
r = bn_divide(a, b, op == '%');
|
| 514 |
+
}
|
| 515 |
+
bn_hasden[nv - 1] = 0;
|
| 516 |
+
bn_vals[nv - 1] = r;
|
| 517 |
+
bn_vsc[nv - 1] = sa;
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
if (can_log && !bn_err && LEN(bn_vals[nv - 1]) <= 62) {
|
| 521 |
+
bn_fmt_slot(nv - 1, sr_str, sizeof sr_str);
|
| 522 |
+
bn_step_cnt++;
|
| 523 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, "step ");
|
| 524 |
+
append_char(bn_steps, sizeof bn_steps, &bn_step_pos, (char)('0' + bn_step_cnt));
|
| 525 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, ": ");
|
| 526 |
+
if (op == 'g') {
|
| 527 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, "gcd(");
|
| 528 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, sa_str);
|
| 529 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, ", ");
|
| 530 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, sb_str);
|
| 531 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, ") = ");
|
| 532 |
+
} else {
|
| 533 |
+
const char *op_s = op == 'd' ? "/" : op == 'f' ? "//" : op == '^' ? "^" :
|
| 534 |
+
op == '*' ? "*" : op == '+' ? "+" : op == '-' ? "-" : "%";
|
| 535 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, sa_str);
|
| 536 |
+
append_char(bn_steps, sizeof bn_steps, &bn_step_pos, ' ');
|
| 537 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, op_s);
|
| 538 |
+
append_char(bn_steps, sizeof bn_steps, &bn_step_pos, ' ');
|
| 539 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, sb_str);
|
| 540 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, " = ");
|
| 541 |
+
}
|
| 542 |
+
append_str(bn_steps, sizeof bn_steps, &bn_step_pos, sr_str);
|
| 543 |
+
append_char(bn_steps, sizeof bn_steps, &bn_step_pos, '\n');
|
| 544 |
+
}
|
| 545 |
+
return nv;
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
// Try evaluating a pure arithmetic/rational/bignum expression in Engine A.
|
| 549 |
+
// Returns 0 on success (having printed the structured trace + exact answer), 1 if unsupported/failed.
|
| 550 |
+
static int run_bignum_engine(const char *expr_ram) {
|
| 551 |
+
bn_used = 0;
|
| 552 |
+
bn_err = 0;
|
| 553 |
+
bn_step_pos = 0;
|
| 554 |
+
bn_step_cnt = 0;
|
| 555 |
+
bn_steps[0] = 0;
|
| 556 |
+
for (int r = 0; r < ONE_REG; r++) {
|
| 557 |
+
bn_mac(BN_OPS(63, 0, 0, r, r, 1));
|
| 558 |
+
bn_mac(BN_OPS(63, 63, 0, r, r, 1));
|
| 559 |
+
bn_dirty[r] = 0;
|
| 560 |
+
}
|
| 561 |
+
bn_ld(&one_ch + 1, BN_OPS(1, 0, 0, ONE_REG, 0, 0));
|
| 562 |
+
|
| 563 |
+
const char *s = expr_ram;
|
| 564 |
+
int operand = 1, nv = 0, no = 0;
|
| 565 |
+
for (;;) {
|
| 566 |
+
char c = *s;
|
| 567 |
+
if (c == ' ' || c == '\t') { s++; continue; }
|
| 568 |
+
if (nv >= 15 || no >= 15) bn_err = 1;
|
| 569 |
+
if (bn_err) break;
|
| 570 |
+
if (operand) {
|
| 571 |
+
if (is_digit(c)) {
|
| 572 |
+
int n = (int)bn_scan(s);
|
| 573 |
+
const char *end = s + n;
|
| 574 |
+
int frac = 0;
|
| 575 |
+
if (*end == '.' && is_digit(end[1])) {
|
| 576 |
+
static char dbuf[MAXD + 4];
|
| 577 |
+
frac = (int)bn_scan(end + 1);
|
| 578 |
+
if (n + frac > MAXD) { bn_err = 1; break; }
|
| 579 |
+
for (int i = 0; i < n; i++) dbuf[i] = s[i];
|
| 580 |
+
for (int i = 0; i < frac; i++) dbuf[n + i] = end[1 + i];
|
| 581 |
+
s = end + 1 + frac;
|
| 582 |
+
n += frac;
|
| 583 |
+
end = dbuf + n;
|
| 584 |
+
} else {
|
| 585 |
+
s = end;
|
| 586 |
+
}
|
| 587 |
+
if (n > MAXD) { bn_err = 1; break; }
|
| 588 |
+
num a = bn_alloc(n);
|
| 589 |
+
bn_ld(end, BN_OPS(n, 0, 0, REG(a), 0, 0));
|
| 590 |
+
bn_wrote(a, n);
|
| 591 |
+
bn_vsc[nv] = frac;
|
| 592 |
+
bn_hasden[nv] = 0;
|
| 593 |
+
bn_vals[nv++] = bn_dig(a, n - 1) ? a : bn_trim(a);
|
| 594 |
+
operand = 0;
|
| 595 |
+
} else if (c == '(') {
|
| 596 |
+
bn_ops[no++] = '(';
|
| 597 |
+
s++;
|
| 598 |
+
} else if (c == '-' || c == '+') {
|
| 599 |
+
if (c == '-') bn_ops[no++] = 'n';
|
| 600 |
+
s++;
|
| 601 |
+
} else if (starts_with_ci(s, "gcd(")) {
|
| 602 |
+
bn_ops[no++] = 'g';
|
| 603 |
+
s += 4;
|
| 604 |
+
} else {
|
| 605 |
+
bn_err = 1;
|
| 606 |
+
}
|
| 607 |
+
continue;
|
| 608 |
+
}
|
| 609 |
+
// Implicit multiplication before '(' when preceded by number or ')'
|
| 610 |
+
if (c == '(') {
|
| 611 |
+
int pr = bn_prec('*');
|
| 612 |
+
while (no && bn_ops[no - 1] != '(' && bn_ops[no - 1] != 'g' && bn_prec(bn_ops[no - 1]) >= pr)
|
| 613 |
+
nv = bn_apply(bn_ops[--no], nv);
|
| 614 |
+
bn_ops[no++] = '*';
|
| 615 |
+
bn_ops[no++] = '(';
|
| 616 |
+
s++;
|
| 617 |
+
operand = 1;
|
| 618 |
+
continue;
|
| 619 |
+
}
|
| 620 |
+
if (c == ')' || c == ',') {
|
| 621 |
+
while (no && bn_ops[no - 1] != '(' && bn_ops[no - 1] != 'g') nv = bn_apply(bn_ops[--no], nv);
|
| 622 |
+
if (!no) {
|
| 623 |
+
bn_err = 1;
|
| 624 |
+
} else if (c == ')') {
|
| 625 |
+
if (bn_ops[--no] == 'g') nv = bn_apply('g', nv);
|
| 626 |
+
} else if (bn_ops[no - 1] != 'g') {
|
| 627 |
+
bn_err = 1;
|
| 628 |
+
} else {
|
| 629 |
+
operand = 1;
|
| 630 |
+
}
|
| 631 |
+
s++;
|
| 632 |
+
continue;
|
| 633 |
+
}
|
| 634 |
+
if (!c) break;
|
| 635 |
+
int op = (unsigned char)c, len = 1;
|
| 636 |
+
if (c == '*' && s[1] == '*') {
|
| 637 |
+
op = '^';
|
| 638 |
+
len = 2;
|
| 639 |
+
} else if (c == '/') {
|
| 640 |
+
op = s[1] == '/' ? 'f' : 'd';
|
| 641 |
+
len = s[1] == '/' ? 2 : 1;
|
| 642 |
+
} else if (c != '+' && c != '-' && c != '*' && c != '%' && c != '^') {
|
| 643 |
+
bn_err = 1;
|
| 644 |
+
continue;
|
| 645 |
+
}
|
| 646 |
+
int pr = bn_prec(op);
|
| 647 |
+
while (no && bn_ops[no - 1] != '(' && bn_ops[no - 1] != 'g' &&
|
| 648 |
+
(bn_prec(bn_ops[no - 1]) > pr || (bn_prec(bn_ops[no - 1]) == pr && op != '^')))
|
| 649 |
+
nv = bn_apply(bn_ops[--no], nv);
|
| 650 |
+
bn_ops[no++] = (char)op;
|
| 651 |
+
s += len;
|
| 652 |
+
operand = 1;
|
| 653 |
+
}
|
| 654 |
+
while (!bn_err && no) {
|
| 655 |
+
int op = bn_ops[--no];
|
| 656 |
+
if (op == '(' || op == 'g') bn_err = 1;
|
| 657 |
+
else nv = bn_apply(op, nv);
|
| 658 |
+
}
|
| 659 |
+
if (bn_err || nv != 1 || operand) return 1;
|
| 660 |
+
|
| 661 |
+
puts_raw("expression: ");
|
| 662 |
+
puts_raw(expr_ram);
|
| 663 |
+
rv_putchar('\n');
|
| 664 |
+
if (bn_step_cnt >= 2) puts_raw(bn_steps);
|
| 665 |
+
|
| 666 |
+
puts_raw("exact value: ");
|
| 667 |
+
num v = bn_vals[0];
|
| 668 |
+
int sc = bn_vsc[0], n = LEN(v);
|
| 669 |
+
if (NEG(v)) rv_putchar('-');
|
| 670 |
+
if (!sc) {
|
| 671 |
+
bn_print((int)REG(v), n);
|
| 672 |
+
} else {
|
| 673 |
+
int z = 0;
|
| 674 |
+
while (z < sc && !bn_dig(v, z)) z++;
|
| 675 |
+
if (n <= sc) rv_putchar('0');
|
| 676 |
+
for (int i = n > sc ? n - 1 : sc - 1; i >= z; i--) {
|
| 677 |
+
if (i == sc - 1) rv_putchar('.');
|
| 678 |
+
rv_putchar((char)('0' + (i < n ? bn_dig(v, i) : 0)));
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
if (bn_hasden[0]) {
|
| 682 |
+
num den = bn_vden[0];
|
| 683 |
+
rv_putchar('/');
|
| 684 |
+
bn_print((int)REG(den), LEN(den));
|
| 685 |
+
// Also print 6-decimal expansion when numerator*10^6 fits in MAXD
|
| 686 |
+
if (n + 6 <= MAXD) {
|
| 687 |
+
uint32_t neg = NEG(v);
|
| 688 |
+
num scaled = bn_shift(bn_copy_of(v & ~0x100u), 6);
|
| 689 |
+
bn_qr dec = bn_divmod(scaled, den);
|
| 690 |
+
int exact_dec = bn_is_zero(dec.r);
|
| 691 |
+
puts_raw(exact_dec ? " (= " : " (~ ");
|
| 692 |
+
if (neg) rv_putchar('-');
|
| 693 |
+
int dn = LEN(dec.q);
|
| 694 |
+
int z = 0;
|
| 695 |
+
while (z < 6 && exact_dec && (z >= dn || !bn_dig(dec.q, z))) z++;
|
| 696 |
+
if (dn <= 6) rv_putchar('0');
|
| 697 |
+
for (int i = dn > 6 ? dn - 1 : 5; i >= z; i--) {
|
| 698 |
+
if (i == 5) rv_putchar('.');
|
| 699 |
+
rv_putchar((char)('0' + (i < dn ? bn_dig(dec.q, i) : 0)));
|
| 700 |
+
}
|
| 701 |
+
rv_putchar(')');
|
| 702 |
+
bn_release(dec.q);
|
| 703 |
+
bn_release(dec.r);
|
| 704 |
+
}
|
| 705 |
+
}
|
| 706 |
+
rv_putchar('\n');
|
| 707 |
+
return 0;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
// ============================================================================
|
| 711 |
+
// Engine B: Q32.32 Fixed-Point Scientific, Equation Solver & Graphing Engine
|
| 712 |
+
// ============================================================================
|
| 713 |
+
|
| 714 |
+
typedef int64_t q32_t;
|
| 715 |
+
#define Q32_MAX 0x7FFFFFFFFFFFFFFFLL
|
| 716 |
+
#define Q32_MIN (-0x7FFFFFFFFFFFFFFFLL - 1LL)
|
| 717 |
+
|
| 718 |
+
static int q_err;
|
| 719 |
+
|
| 720 |
+
static q32_t q32_from_int(int64_t x) {
|
| 721 |
+
if (x > 2147483647LL || x < -2147483648LL) { q_err = 1; return x > 0 ? Q32_MAX : Q32_MIN; }
|
| 722 |
+
return x << 32;
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
static q32_t q32_add(q32_t a, q32_t b) {
|
| 726 |
+
uint64_t r = (uint64_t)a + (uint64_t)b;
|
| 727 |
+
if (((a ^ (q32_t)r) & (b ^ (q32_t)r)) < 0) {
|
| 728 |
+
q_err = 1;
|
| 729 |
+
return a > 0 ? Q32_MAX : Q32_MIN;
|
| 730 |
+
}
|
| 731 |
+
return (q32_t)r;
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
static q32_t q32_sub(q32_t a, q32_t b) {
|
| 735 |
+
uint64_t r = (uint64_t)a - (uint64_t)b;
|
| 736 |
+
if (((a ^ b) & (a ^ (q32_t)r)) < 0) {
|
| 737 |
+
q_err = 1;
|
| 738 |
+
return a > 0 ? Q32_MAX : Q32_MIN;
|
| 739 |
+
}
|
| 740 |
+
return (q32_t)r;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
static q32_t q32_mul(q32_t a, q32_t b) {
|
| 744 |
+
int neg = (a < 0) ^ (b < 0);
|
| 745 |
+
uint64_t ua = a < 0 ? -(uint64_t)a : (uint64_t)a;
|
| 746 |
+
uint64_t ub = b < 0 ? -(uint64_t)b : (uint64_t)b;
|
| 747 |
+
uint32_t al = (uint32_t)ua, ah = (uint32_t)(ua >> 32);
|
| 748 |
+
uint32_t bl = (uint32_t)ub, bh = (uint32_t)(ub >> 32);
|
| 749 |
+
if (ah && bh) {
|
| 750 |
+
uint64_t hh = (uint64_t)ah * bh;
|
| 751 |
+
if (hh >= 0x80000000ULL) { q_err = 1; return neg ? Q32_MIN : Q32_MAX; }
|
| 752 |
+
uint64_t p_ll = (uint64_t)al * bl;
|
| 753 |
+
uint64_t p_hl = (uint64_t)ah * bl;
|
| 754 |
+
uint64_t p_lh = (uint64_t)al * bh;
|
| 755 |
+
uint64_t mid = (p_ll >> 32) + (p_hl & 0xFFFFFFFFULL) + (p_lh & 0xFFFFFFFFULL);
|
| 756 |
+
uint64_t hi = hh + (p_hl >> 32) + (p_lh >> 32) + (mid >> 32);
|
| 757 |
+
if (hi >= 0x80000000ULL) { q_err = 1; return neg ? Q32_MIN : Q32_MAX; }
|
| 758 |
+
uint64_t res = (hi << 32) | (mid & 0xFFFFFFFFULL);
|
| 759 |
+
res += (p_ll >> 31) & 1ULL;
|
| 760 |
+
return neg ? -(q32_t)res : (q32_t)res;
|
| 761 |
+
}
|
| 762 |
+
uint64_t p_ll = (uint64_t)al * bl;
|
| 763 |
+
uint64_t p_hl = (uint64_t)ah * bl;
|
| 764 |
+
uint64_t p_lh = (uint64_t)al * bh;
|
| 765 |
+
uint64_t mid = (p_ll >> 32) + (p_hl & 0xFFFFFFFFULL) + (p_lh & 0xFFFFFFFFULL);
|
| 766 |
+
uint64_t hi = (p_hl >> 32) + (p_lh >> 32) + (mid >> 32);
|
| 767 |
+
if (hi >= 0x80000000ULL) { q_err = 1; return neg ? Q32_MIN : Q32_MAX; }
|
| 768 |
+
uint64_t res = (hi << 32) | (mid & 0xFFFFFFFFULL);
|
| 769 |
+
res += (p_ll >> 31) & 1ULL;
|
| 770 |
+
return neg ? -(q32_t)res : (q32_t)res;
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
static q32_t q32_div(q32_t a, q32_t b) {
|
| 774 |
+
if (b == 0) { q_err = 1; return 0; }
|
| 775 |
+
int neg = (a < 0) ^ (b < 0);
|
| 776 |
+
uint64_t ua = a < 0 ? -(uint64_t)a : (uint64_t)a;
|
| 777 |
+
uint64_t ub = b < 0 ? -(uint64_t)b : (uint64_t)b;
|
| 778 |
+
uint64_t q_int = ua / ub;
|
| 779 |
+
if (q_int >= 0x80000000ULL) { q_err = 1; return neg ? Q32_MIN : Q32_MAX; }
|
| 780 |
+
uint64_t rem = ua % ub;
|
| 781 |
+
uint32_t frac = 0;
|
| 782 |
+
if (ub <= 0xFFFFFFFFULL) {
|
| 783 |
+
frac = (uint32_t)(((rem << 32) + (ub >> 1)) / ub);
|
| 784 |
+
uint64_t res = (q_int << 32) + frac;
|
| 785 |
+
return neg ? -(q32_t)res : (q32_t)res;
|
| 786 |
+
}
|
| 787 |
+
for (int i = 0; i < 32; i++) {
|
| 788 |
+
uint64_t top = rem >> 63;
|
| 789 |
+
rem <<= 1;
|
| 790 |
+
frac <<= 1;
|
| 791 |
+
if (top || rem >= ub) {
|
| 792 |
+
rem -= ub;
|
| 793 |
+
frac |= 1u;
|
| 794 |
+
}
|
| 795 |
+
}
|
| 796 |
+
uint64_t top = rem >> 63;
|
| 797 |
+
rem <<= 1;
|
| 798 |
+
uint64_t res = (q_int << 32) | frac;
|
| 799 |
+
if (top || rem >= ub) res++;
|
| 800 |
+
return neg ? -(q32_t)res : (q32_t)res;
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
static q32_t q32_sqrt(q32_t x) {
|
| 804 |
+
if (x < 0) { q_err = 1; return 0; }
|
| 805 |
+
if (x == 0) return 0;
|
| 806 |
+
uint64_t ux = (uint64_t)x;
|
| 807 |
+
uint64_t rem = 0, root = 0;
|
| 808 |
+
for (int i = 47; i >= 0; i--) {
|
| 809 |
+
uint64_t two = i >= 16 ? ((ux >> (2 * (i - 16))) & 3ULL) : 0ULL;
|
| 810 |
+
rem = (rem << 2) | two;
|
| 811 |
+
uint64_t cand = (root << 2) | 1ULL;
|
| 812 |
+
if (rem >= cand) {
|
| 813 |
+
rem -= cand;
|
| 814 |
+
root = (root << 1) | 1ULL;
|
| 815 |
+
} else {
|
| 816 |
+
root <<= 1;
|
| 817 |
+
}
|
| 818 |
+
}
|
| 819 |
+
return (q32_t)root;
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
static void q32_sincos(q32_t theta, q32_t *out_s, q32_t *out_c) {
|
| 823 |
+
q32_t r = theta;
|
| 824 |
+
if (r > Q32_PI || r < -Q32_PI) {
|
| 825 |
+
int save = q_err;
|
| 826 |
+
int64_t turns = q32_mul(r, Q32_INV_TWO_PI) >> 32;
|
| 827 |
+
q_err = save;
|
| 828 |
+
r -= turns * Q32_TWO_PI;
|
| 829 |
+
while (r > Q32_PI) r -= Q32_TWO_PI;
|
| 830 |
+
while (r < -Q32_PI) r += Q32_TWO_PI;
|
| 831 |
+
}
|
| 832 |
+
int flip = 0;
|
| 833 |
+
if (r > Q32_HALF_PI) { r = Q32_PI - r; flip = 1; }
|
| 834 |
+
else if (r < -Q32_HALF_PI) { r = -Q32_PI - r; flip = 1; }
|
| 835 |
+
|
| 836 |
+
q32_t x = CORDIC_K, y = 0, z = r;
|
| 837 |
+
for (int i = 0; i < 30; i++) {
|
| 838 |
+
q32_t dx = y >> i, dy = x >> i;
|
| 839 |
+
if (z >= 0) { x -= dx; y += dy; z -= CORDIC_ATAN[i]; }
|
| 840 |
+
else { x += dx; y -= dy; z += CORDIC_ATAN[i]; }
|
| 841 |
+
}
|
| 842 |
+
if (flip) x = -x;
|
| 843 |
+
if (x > -16 && x < 16) x = 0;
|
| 844 |
+
if (y > -16 && y < 16) y = 0;
|
| 845 |
+
*out_s = y;
|
| 846 |
+
*out_c = x;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
static q32_t q32_ln(q32_t x) {
|
| 850 |
+
if (x <= 0) { q_err = 1; return 0; }
|
| 851 |
+
uint64_t m = (uint64_t)x;
|
| 852 |
+
int k = 0;
|
| 853 |
+
while (m >= (2ULL << 32)) { m >>= 1; k++; }
|
| 854 |
+
while (m < (1ULL << 32)) { m <<= 1; k--; }
|
| 855 |
+
uint64_t prod = 1ULL << 32;
|
| 856 |
+
q32_t acc = 0;
|
| 857 |
+
for (int i = 1; i <= 30; i++) {
|
| 858 |
+
uint64_t nxt = prod + (prod >> i);
|
| 859 |
+
if (nxt <= m) {
|
| 860 |
+
prod = nxt;
|
| 861 |
+
acc += LN1P_POW2[i];
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
if (m > prod) acc += q32_div((q32_t)(m - prod), (q32_t)prod);
|
| 865 |
+
return (int64_t)k * Q32_LN2 + acc;
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
static q32_t q32_exp(q32_t x) {
|
| 869 |
+
if (x > 92288000000LL) { q_err = 1; return Q32_MAX; }
|
| 870 |
+
if (x < -96000000000LL) return 0;
|
| 871 |
+
q32_t kq = q32_mul(x, Q32_INV_LN2);
|
| 872 |
+
int32_t k = (int32_t)(kq >> 32);
|
| 873 |
+
q32_t r = x - (int64_t)k * Q32_LN2;
|
| 874 |
+
while (r < 0) { r += Q32_LN2; k--; }
|
| 875 |
+
while (r >= Q32_LN2) { r -= Q32_LN2; k++; }
|
| 876 |
+
uint64_t prod = 1ULL << 32;
|
| 877 |
+
for (int i = 1; i <= 30; i++) {
|
| 878 |
+
if (r >= LN1P_POW2[i]) {
|
| 879 |
+
r -= LN1P_POW2[i];
|
| 880 |
+
prod += prod >> i;
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
if (r > 0) prod += (prod * (uint64_t)r) >> 32;
|
| 884 |
+
if (k >= 31) { q_err = 1; return Q32_MAX; }
|
| 885 |
+
if (k >= 0) {
|
| 886 |
+
uint64_t res = prod << k;
|
| 887 |
+
if (res >= 0x8000000000000000ULL) { q_err = 1; return Q32_MAX; }
|
| 888 |
+
return (q32_t)res;
|
| 889 |
+
}
|
| 890 |
+
return (-k >= 63) ? 0 : (q32_t)(prod >> (-k));
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
static q32_t q32_ipow(q32_t a, int32_t e) {
|
| 894 |
+
int inv = 0;
|
| 895 |
+
if (e < 0) { inv = 1; e = -e; }
|
| 896 |
+
q32_t res = Q32_ONE;
|
| 897 |
+
while (e > 0 && !q_err) {
|
| 898 |
+
if (e & 1) res = q32_mul(res, a);
|
| 899 |
+
e >>= 1;
|
| 900 |
+
if (e) a = q32_mul(a, a);
|
| 901 |
+
}
|
| 902 |
+
return inv ? q32_div(Q32_ONE, res) : res;
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
static q32_t q32_pow(q32_t a, q32_t b) {
|
| 906 |
+
if ((b & 0xFFFFFFFFLL) == 0) {
|
| 907 |
+
int64_t e = b >> 32;
|
| 908 |
+
if (e >= -64 && e <= 64) return q32_ipow(a, (int32_t)e);
|
| 909 |
+
}
|
| 910 |
+
if (a == 0) {
|
| 911 |
+
if (b <= 0) q_err = 1;
|
| 912 |
+
return 0;
|
| 913 |
+
}
|
| 914 |
+
if (a < 0) { q_err = 1; return 0; }
|
| 915 |
+
return q32_exp(q32_mul(b, q32_ln(a)));
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
static int parse_q32_num(const char *s, const char **endp, q32_t *out) {
|
| 919 |
+
while (*s == ' ') s++;
|
| 920 |
+
int neg = 0;
|
| 921 |
+
if (*s == '-' || *s == '+') { neg = (*s == '-'); s++; }
|
| 922 |
+
while (*s == ' ') s++;
|
| 923 |
+
if (starts_with_ci(s, "pi") && !is_alpha(s[2])) {
|
| 924 |
+
*out = neg ? -Q32_PI : Q32_PI;
|
| 925 |
+
if (endp) *endp = s + 2;
|
| 926 |
+
return 1;
|
| 927 |
+
}
|
| 928 |
+
if (!is_digit(*s) && !(*s == '.' && is_digit(s[1]))) return 0;
|
| 929 |
+
uint64_t ip = 0;
|
| 930 |
+
while (is_digit(*s)) {
|
| 931 |
+
ip = ip * 10 + (uint64_t)(*s - '0');
|
| 932 |
+
if (ip > 2147483647ULL) return 0;
|
| 933 |
+
s++;
|
| 934 |
+
}
|
| 935 |
+
uint64_t fp_num = 0, fp_den = 1;
|
| 936 |
+
if (*s == '.') {
|
| 937 |
+
s++;
|
| 938 |
+
while (is_digit(*s)) {
|
| 939 |
+
if (fp_den < 100000000000ULL) {
|
| 940 |
+
fp_num = fp_num * 10 + (uint64_t)(*s - '0');
|
| 941 |
+
fp_den *= 10;
|
| 942 |
+
}
|
| 943 |
+
s++;
|
| 944 |
+
}
|
| 945 |
+
}
|
| 946 |
+
uint64_t frac = 0;
|
| 947 |
+
if (fp_num > 0) {
|
| 948 |
+
// Compute (fp_num << 32) / fp_den cleanly via 32-bit binary division
|
| 949 |
+
uint64_t rem = fp_num;
|
| 950 |
+
for (int i = 0; i < 32; i++) {
|
| 951 |
+
rem <<= 1;
|
| 952 |
+
frac <<= 1;
|
| 953 |
+
if (rem >= fp_den) { rem -= fp_den; frac |= 1ULL; }
|
| 954 |
+
}
|
| 955 |
+
if ((rem << 1) >= fp_den) frac++;
|
| 956 |
+
}
|
| 957 |
+
q32_t v = (q32_t)((ip << 32) + frac);
|
| 958 |
+
// Optional /denom for inline bounds like "-pi/2" or "3/2"
|
| 959 |
+
if (*s == '/' && is_digit(s[1])) {
|
| 960 |
+
s++;
|
| 961 |
+
int64_t d = 0;
|
| 962 |
+
while (is_digit(*s) && d < 100000000LL) { d = d * 10 + (*s - '0'); s++; }
|
| 963 |
+
if (d > 0) v = q32_div(v, q32_from_int(d));
|
| 964 |
+
}
|
| 965 |
+
*out = neg ? -v : v;
|
| 966 |
+
if (endp) *endp = s;
|
| 967 |
+
return 1;
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
static void fmt_q32(q32_t v, char *buf, int cap, int decimals) {
|
| 971 |
+
int p = 0;
|
| 972 |
+
if (v > -64 && v < 64) v = 0; // snap sub-1.5e-8 noise to 0
|
| 973 |
+
int neg = v < 0;
|
| 974 |
+
uint64_t uv = neg ? -(uint64_t)v : (uint64_t)v;
|
| 975 |
+
uint32_t scale = 1;
|
| 976 |
+
for (int i = 0; i < decimals; i++) scale *= 10u;
|
| 977 |
+
uint32_t ip = (uint32_t)(uv >> 32);
|
| 978 |
+
uint64_t fl = uv & 0xFFFFFFFFULL;
|
| 979 |
+
// Rounded decimal fraction: ((fl * scale) + 2^31) >> 32
|
| 980 |
+
uint64_t prod = fl * (uint64_t)scale + (1ULL << 31);
|
| 981 |
+
uint32_t dec = (uint32_t)(prod >> 32);
|
| 982 |
+
if (dec >= scale) { ip++; dec -= scale; }
|
| 983 |
+
if (ip == 0 && dec == 0) neg = 0;
|
| 984 |
+
if (neg) append_char(buf, cap, &p, '-');
|
| 985 |
+
|
| 986 |
+
char itmp[16];
|
| 987 |
+
int in = 0;
|
| 988 |
+
if (ip == 0) itmp[in++] = '0';
|
| 989 |
+
while (ip > 0) { itmp[in++] = (char)('0' + (ip % 10u)); ip /= 10u; }
|
| 990 |
+
while (in > 0) append_char(buf, cap, &p, itmp[--in]);
|
| 991 |
+
|
| 992 |
+
if (decimals > 0 && dec > 0) {
|
| 993 |
+
char dtmp[12];
|
| 994 |
+
for (int i = decimals - 1; i >= 0; i--) {
|
| 995 |
+
dtmp[i] = (char)('0' + (dec % 10u));
|
| 996 |
+
dec /= 10u;
|
| 997 |
+
}
|
| 998 |
+
int len = decimals;
|
| 999 |
+
while (len > 0 && dtmp[len - 1] == '0') len--;
|
| 1000 |
+
if (len > 0) {
|
| 1001 |
+
append_char(buf, cap, &p, '.');
|
| 1002 |
+
for (int i = 0; i < len; i++) append_char(buf, cap, &p, dtmp[i]);
|
| 1003 |
+
}
|
| 1004 |
+
}
|
| 1005 |
+
}
|
| 1006 |
+
|
| 1007 |
+
// Continued-fraction recovery for exact small rationals p/q (1 < q <= 240, error <= 6 ULPs).
|
| 1008 |
+
static void fmt_q32_rich(q32_t v, char *buf, int cap) {
|
| 1009 |
+
fmt_q32(v, buf, cap, 6);
|
| 1010 |
+
uint64_t uv = v < 0 ? -(uint64_t)v : (uint64_t)v;
|
| 1011 |
+
if ((uv & 0xFFFFFFFFULL) < 64 || (uv & 0xFFFFFFFFULL) > 0xFFFFFFC0ULL) return;
|
| 1012 |
+
int64_t p0 = 0, q0 = 1, p1 = 1, q1 = 0;
|
| 1013 |
+
uint64_t num = uv, den = 1ULL << 32;
|
| 1014 |
+
for (int step = 0; step < 12 && den != 0; step++) {
|
| 1015 |
+
int64_t a = (int64_t)(num / den);
|
| 1016 |
+
uint64_t r = num % den;
|
| 1017 |
+
int64_t p2 = a * p1 + p0;
|
| 1018 |
+
int64_t q2 = a * q1 + q0;
|
| 1019 |
+
if (q2 > 240) break;
|
| 1020 |
+
p0 = p1; q0 = q1; p1 = p2; q1 = q2;
|
| 1021 |
+
if (q1 > 1) {
|
| 1022 |
+
int save = q_err;
|
| 1023 |
+
q_err = 0;
|
| 1024 |
+
q32_t cand = q32_div(q32_from_int(v < 0 ? -p1 : p1), q32_from_int(q1));
|
| 1025 |
+
q_err = save;
|
| 1026 |
+
q32_t diff = v > cand ? v - cand : cand - v;
|
| 1027 |
+
if (diff <= 6) {
|
| 1028 |
+
char dec_buf[32];
|
| 1029 |
+
fmt_q32(v, dec_buf, sizeof dec_buf, 6);
|
| 1030 |
+
int pos = 0;
|
| 1031 |
+
if (v < 0) append_char(buf, cap, &pos, '-');
|
| 1032 |
+
char tmp[24];
|
| 1033 |
+
int tn = 0;
|
| 1034 |
+
uint32_t up = (uint32_t)p1;
|
| 1035 |
+
while (up > 0) { tmp[tn++] = (char)('0' + (up % 10u)); up /= 10u; }
|
| 1036 |
+
while (tn > 0) append_char(buf, cap, &pos, tmp[--tn]);
|
| 1037 |
+
append_char(buf, cap, &pos, '/');
|
| 1038 |
+
uint32_t uq = (uint32_t)q1;
|
| 1039 |
+
while (uq > 0) { tmp[tn++] = (char)('0' + (uq % 10u)); uq /= 10u; }
|
| 1040 |
+
while (tn > 0) append_char(buf, cap, &pos, tmp[--tn]);
|
| 1041 |
+
append_str(buf, cap, &pos, " (~");
|
| 1042 |
+
append_str(buf, cap, &pos, dec_buf);
|
| 1043 |
+
append_char(buf, cap, &pos, ')');
|
| 1044 |
+
return;
|
| 1045 |
+
}
|
| 1046 |
+
}
|
| 1047 |
+
num = den;
|
| 1048 |
+
den = r;
|
| 1049 |
+
}
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
// --- RPN Bytecode Compiler & Evaluator ---
|
| 1053 |
+
enum {
|
| 1054 |
+
OP_NUM = 1, OP_VAR_X,
|
| 1055 |
+
OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_POW, OP_NEG,
|
| 1056 |
+
OP_SIN, OP_COS, OP_TAN, OP_SQRT, OP_ABS, OP_EXP, OP_LN, OP_LOG10
|
| 1057 |
+
};
|
| 1058 |
+
|
| 1059 |
+
typedef struct {
|
| 1060 |
+
uint8_t op;
|
| 1061 |
+
q32_t val;
|
| 1062 |
+
} RpnInst;
|
| 1063 |
+
|
| 1064 |
+
typedef struct {
|
| 1065 |
+
RpnInst code[96];
|
| 1066 |
+
int n;
|
| 1067 |
+
int uses_x;
|
| 1068 |
+
} RpnProg;
|
| 1069 |
+
|
| 1070 |
+
static int rpn_prec(int op) {
|
| 1071 |
+
if (op >= OP_SIN) return 5;
|
| 1072 |
+
if (op == OP_POW) return 4;
|
| 1073 |
+
if (op == OP_NEG) return 3;
|
| 1074 |
+
if (op == OP_MUL || op == OP_DIV) return 2;
|
| 1075 |
+
return 1;
|
| 1076 |
+
}
|
| 1077 |
+
|
| 1078 |
+
static int compile_rpn(const char *s, RpnProg *prog) {
|
| 1079 |
+
prog->n = 0;
|
| 1080 |
+
prog->uses_x = 0;
|
| 1081 |
+
uint8_t op_stk[64];
|
| 1082 |
+
int no = 0, operand = 1;
|
| 1083 |
+
|
| 1084 |
+
while (*s) {
|
| 1085 |
+
if (is_space(*s)) { s++; continue; }
|
| 1086 |
+
if (prog->n >= 92 || no >= 60) return 0;
|
| 1087 |
+
|
| 1088 |
+
// Implicit multiplication when an atom/group just finished (`operand == 0`)
|
| 1089 |
+
// and the next token starts an operand (`x`, `pi`, `e`, `(`, or function name, or digit after `)`)
|
| 1090 |
+
if (!operand) {
|
| 1091 |
+
if (*s == '(' || is_alpha(*s)) {
|
| 1092 |
+
int pr = rpn_prec(OP_MUL);
|
| 1093 |
+
while (no && op_stk[no - 1] != '(' && rpn_prec(op_stk[no - 1]) >= pr)
|
| 1094 |
+
prog->code[prog->n++] = (RpnInst){op_stk[--no], 0};
|
| 1095 |
+
op_stk[no++] = OP_MUL;
|
| 1096 |
+
operand = 1;
|
| 1097 |
+
}
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
if (operand) {
|
| 1101 |
+
if (*s == '(') {
|
| 1102 |
+
op_stk[no++] = '(';
|
| 1103 |
+
s++;
|
| 1104 |
+
} else if (*s == '-' || *s == '+') {
|
| 1105 |
+
if (*s == '-') op_stk[no++] = OP_NEG;
|
| 1106 |
+
s++;
|
| 1107 |
+
} else if (is_digit(*s) || (*s == '.' && is_digit(s[1]))) {
|
| 1108 |
+
const char *end = s;
|
| 1109 |
+
// Do not let parse_q32_num swallow `/denom` inside general expressions!
|
| 1110 |
+
uint64_t ip = 0;
|
| 1111 |
+
while (is_digit(*end)) {
|
| 1112 |
+
ip = ip * 10 + (uint64_t)(*end - '0');
|
| 1113 |
+
if (ip > 2147483647ULL) return 0;
|
| 1114 |
+
end++;
|
| 1115 |
+
}
|
| 1116 |
+
uint64_t fp_num = 0, fp_den = 1;
|
| 1117 |
+
if (*end == '.') {
|
| 1118 |
+
end++;
|
| 1119 |
+
while (is_digit(*end)) {
|
| 1120 |
+
if (fp_den < 100000000000ULL) {
|
| 1121 |
+
fp_num = fp_num * 10 + (uint64_t)(*end - '0');
|
| 1122 |
+
fp_den *= 10;
|
| 1123 |
+
}
|
| 1124 |
+
end++;
|
| 1125 |
+
}
|
| 1126 |
+
}
|
| 1127 |
+
uint64_t frac = 0;
|
| 1128 |
+
if (fp_num > 0) {
|
| 1129 |
+
uint64_t rem = fp_num;
|
| 1130 |
+
for (int i = 0; i < 32; i++) {
|
| 1131 |
+
rem <<= 1; frac <<= 1;
|
| 1132 |
+
if (rem >= fp_den) { rem -= fp_den; frac |= 1ULL; }
|
| 1133 |
+
}
|
| 1134 |
+
if ((rem << 1) >= fp_den) frac++;
|
| 1135 |
+
}
|
| 1136 |
+
prog->code[prog->n++] = (RpnInst){OP_NUM, (q32_t)((ip << 32) + frac)};
|
| 1137 |
+
s = end;
|
| 1138 |
+
operand = 0;
|
| 1139 |
+
} else if (is_alpha(*s)) {
|
| 1140 |
+
char id[16];
|
| 1141 |
+
int il = 0;
|
| 1142 |
+
while (is_alpha(*s) || is_digit(*s)) {
|
| 1143 |
+
if (il < 15) id[il++] = (*s >= 'A' && *s <= 'Z') ? (char)(*s + 32) : *s;
|
| 1144 |
+
s++;
|
| 1145 |
+
}
|
| 1146 |
+
id[il] = 0;
|
| 1147 |
+
while (*s == ' ') s++;
|
| 1148 |
+
if (streq(id, "x") || streq(id, "t")) {
|
| 1149 |
+
prog->code[prog->n++] = (RpnInst){OP_VAR_X, 0};
|
| 1150 |
+
prog->uses_x = 1;
|
| 1151 |
+
operand = 0;
|
| 1152 |
+
} else if (streq(id, "pi")) {
|
| 1153 |
+
prog->code[prog->n++] = (RpnInst){OP_NUM, Q32_PI};
|
| 1154 |
+
operand = 0;
|
| 1155 |
+
} else if (streq(id, "e")) {
|
| 1156 |
+
prog->code[prog->n++] = (RpnInst){OP_NUM, Q32_E};
|
| 1157 |
+
operand = 0;
|
| 1158 |
+
} else if (*s == '(') {
|
| 1159 |
+
uint8_t fn = 0;
|
| 1160 |
+
if (streq(id, "sin")) fn = OP_SIN;
|
| 1161 |
+
else if (streq(id, "cos")) fn = OP_COS;
|
| 1162 |
+
else if (streq(id, "tan")) fn = OP_TAN;
|
| 1163 |
+
else if (streq(id, "sqrt")) fn = OP_SQRT;
|
| 1164 |
+
else if (streq(id, "abs")) fn = OP_ABS;
|
| 1165 |
+
else if (streq(id, "exp")) fn = OP_EXP;
|
| 1166 |
+
else if (streq(id, "ln") || streq(id, "log")) fn = OP_LN;
|
| 1167 |
+
else if (streq(id, "log10")) fn = OP_LOG10;
|
| 1168 |
+
else return 0;
|
| 1169 |
+
op_stk[no++] = fn;
|
| 1170 |
+
op_stk[no++] = '(';
|
| 1171 |
+
s++; // consume '('
|
| 1172 |
+
} else {
|
| 1173 |
+
return 0;
|
| 1174 |
+
}
|
| 1175 |
+
} else {
|
| 1176 |
+
return 0;
|
| 1177 |
+
}
|
| 1178 |
+
continue;
|
| 1179 |
+
}
|
| 1180 |
+
|
| 1181 |
+
if (*s == ')') {
|
| 1182 |
+
while (no && op_stk[no - 1] != '(')
|
| 1183 |
+
prog->code[prog->n++] = (RpnInst){op_stk[--no], 0};
|
| 1184 |
+
if (!no) return 0;
|
| 1185 |
+
no--; // pop '('
|
| 1186 |
+
if (no && op_stk[no - 1] >= OP_SIN)
|
| 1187 |
+
prog->code[prog->n++] = (RpnInst){op_stk[--no], 0};
|
| 1188 |
+
s++;
|
| 1189 |
+
continue;
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
uint8_t op = 0;
|
| 1193 |
+
int adv = 1;
|
| 1194 |
+
if (*s == '+') op = OP_ADD;
|
| 1195 |
+
else if (*s == '-') op = OP_SUB;
|
| 1196 |
+
else if (*s == '*' && s[1] == '*') { op = OP_POW; adv = 2; }
|
| 1197 |
+
else if (*s == '*') op = OP_MUL;
|
| 1198 |
+
else if (*s == '/') op = OP_DIV;
|
| 1199 |
+
else if (*s == '^') op = OP_POW;
|
| 1200 |
+
else return 0;
|
| 1201 |
+
|
| 1202 |
+
int pr = rpn_prec(op);
|
| 1203 |
+
while (no && op_stk[no - 1] != '(' &&
|
| 1204 |
+
(rpn_prec(op_stk[no - 1]) > pr || (rpn_prec(op_stk[no - 1]) == pr && op != OP_POW)))
|
| 1205 |
+
prog->code[prog->n++] = (RpnInst){op_stk[--no], 0};
|
| 1206 |
+
op_stk[no++] = op;
|
| 1207 |
+
s += adv;
|
| 1208 |
+
operand = 1;
|
| 1209 |
+
}
|
| 1210 |
+
|
| 1211 |
+
if (operand) return 0;
|
| 1212 |
+
while (no) {
|
| 1213 |
+
if (op_stk[no - 1] == '(') return 0;
|
| 1214 |
+
prog->code[prog->n++] = (RpnInst){op_stk[--no], 0};
|
| 1215 |
+
}
|
| 1216 |
+
return prog->n > 0;
|
| 1217 |
+
}
|
| 1218 |
+
|
| 1219 |
+
static q32_t eval_rpn(const RpnProg *prog, q32_t x_val, int *ok, char *step_buf, int step_cap) {
|
| 1220 |
+
q32_t stk[48];
|
| 1221 |
+
int sp = 0, step_pos = 0, step_cnt = 0;
|
| 1222 |
+
q_err = 0;
|
| 1223 |
+
if (step_buf && step_cap > 0) step_buf[0] = 0;
|
| 1224 |
+
|
| 1225 |
+
for (int i = 0; i < prog->n && !q_err; i++) {
|
| 1226 |
+
uint8_t op = prog->code[i].op;
|
| 1227 |
+
if (op == OP_NUM) {
|
| 1228 |
+
stk[sp++] = prog->code[i].val;
|
| 1229 |
+
} else if (op == OP_VAR_X) {
|
| 1230 |
+
stk[sp++] = x_val;
|
| 1231 |
+
} else if (op == OP_NEG || op >= OP_SIN) {
|
| 1232 |
+
if (sp < 1) { q_err = 1; break; }
|
| 1233 |
+
q32_t a = stk[sp - 1], r = 0, s_tmp, c_tmp;
|
| 1234 |
+
const char *fn_name = NULL;
|
| 1235 |
+
if (op == OP_NEG) r = -a;
|
| 1236 |
+
else if (op == OP_SIN) { q32_sincos(a, &s_tmp, &c_tmp); r = s_tmp; fn_name = "sin"; }
|
| 1237 |
+
else if (op == OP_COS) { q32_sincos(a, &s_tmp, &c_tmp); r = c_tmp; fn_name = "cos"; }
|
| 1238 |
+
else if (op == OP_TAN) {
|
| 1239 |
+
q32_sincos(a, &s_tmp, &c_tmp);
|
| 1240 |
+
if (c_tmp > -4096 && c_tmp < 4096) q_err = 1;
|
| 1241 |
+
else r = q32_div(s_tmp, c_tmp);
|
| 1242 |
+
fn_name = "tan";
|
| 1243 |
+
}
|
| 1244 |
+
else if (op == OP_SQRT) { r = q32_sqrt(a); fn_name = "sqrt"; }
|
| 1245 |
+
else if (op == OP_ABS) { r = a < 0 ? -a : a; fn_name = "abs"; }
|
| 1246 |
+
else if (op == OP_EXP) { r = q32_exp(a); fn_name = "exp"; }
|
| 1247 |
+
else if (op == OP_LN) { r = q32_ln(a); fn_name = "ln"; }
|
| 1248 |
+
else if (op == OP_LOG10) { r = q32_div(q32_ln(a), Q32_LN10); fn_name = "log10"; }
|
| 1249 |
+
stk[sp - 1] = r;
|
| 1250 |
+
if (step_buf && fn_name && !q_err && step_cnt < 8) {
|
| 1251 |
+
char ba[32], br[32];
|
| 1252 |
+
fmt_q32(a, ba, sizeof ba, 6);
|
| 1253 |
+
fmt_q32(r, br, sizeof br, 6);
|
| 1254 |
+
step_cnt++;
|
| 1255 |
+
append_str(step_buf, step_cap, &step_pos, "step ");
|
| 1256 |
+
append_char(step_buf, step_cap, &step_pos, (char)('0' + step_cnt));
|
| 1257 |
+
append_str(step_buf, step_cap, &step_pos, ": ");
|
| 1258 |
+
append_str(step_buf, step_cap, &step_pos, fn_name);
|
| 1259 |
+
append_char(step_buf, step_cap, &step_pos, '(');
|
| 1260 |
+
append_str(step_buf, step_cap, &step_pos, ba);
|
| 1261 |
+
append_str(step_buf, step_cap, &step_pos, ") = ");
|
| 1262 |
+
append_str(step_buf, step_cap, &step_pos, br);
|
| 1263 |
+
append_char(step_buf, step_cap, &step_pos, '\n');
|
| 1264 |
+
}
|
| 1265 |
+
} else {
|
| 1266 |
+
if (sp < 2) { q_err = 1; break; }
|
| 1267 |
+
q32_t b = stk[--sp], a = stk[sp - 1], r = 0;
|
| 1268 |
+
const char *op_s = "+";
|
| 1269 |
+
if (op == OP_ADD) { r = q32_add(a, b); op_s = "+"; }
|
| 1270 |
+
else if (op == OP_SUB) { r = q32_sub(a, b); op_s = "-"; }
|
| 1271 |
+
else if (op == OP_MUL) { r = q32_mul(a, b); op_s = "*"; }
|
| 1272 |
+
else if (op == OP_DIV) { r = q32_div(a, b); op_s = "/"; }
|
| 1273 |
+
else if (op == OP_POW) { r = q32_pow(a, b); op_s = "^"; }
|
| 1274 |
+
stk[sp - 1] = r;
|
| 1275 |
+
if (step_buf && !q_err && step_cnt < 8) {
|
| 1276 |
+
char ba[32], bb[32], br[32];
|
| 1277 |
+
fmt_q32(a, ba, sizeof ba, 6);
|
| 1278 |
+
fmt_q32(b, bb, sizeof bb, 6);
|
| 1279 |
+
fmt_q32(r, br, sizeof br, 6);
|
| 1280 |
+
step_cnt++;
|
| 1281 |
+
append_str(step_buf, step_cap, &step_pos, "step ");
|
| 1282 |
+
append_char(step_buf, step_cap, &step_pos, (char)('0' + step_cnt));
|
| 1283 |
+
append_str(step_buf, step_cap, &step_pos, ": ");
|
| 1284 |
+
append_str(step_buf, step_cap, &step_pos, ba);
|
| 1285 |
+
append_char(step_buf, step_cap, &step_pos, ' ');
|
| 1286 |
+
append_str(step_buf, step_cap, &step_pos, op_s);
|
| 1287 |
+
append_char(step_buf, step_cap, &step_pos, ' ');
|
| 1288 |
+
append_str(step_buf, step_cap, &step_pos, bb);
|
| 1289 |
+
append_str(step_buf, step_cap, &step_pos, " = ");
|
| 1290 |
+
append_str(step_buf, step_cap, &step_pos, br);
|
| 1291 |
+
append_char(step_buf, step_cap, &step_pos, '\n');
|
| 1292 |
+
}
|
| 1293 |
+
}
|
| 1294 |
+
}
|
| 1295 |
+
if (q_err || sp != 1) {
|
| 1296 |
+
if (ok) *ok = 0;
|
| 1297 |
+
return 0;
|
| 1298 |
+
}
|
| 1299 |
+
if (ok) *ok = 1;
|
| 1300 |
+
return stk[0];
|
| 1301 |
+
}
|
| 1302 |
+
|
| 1303 |
+
// Detect exact degree <= 2 polynomial f(x) = a*x^2 + b*x + c using 7-point verification.
|
| 1304 |
+
// Returns degree (0, 1, 2) or -1 if non-quadratic / nonlinear.
|
| 1305 |
+
static int detect_poly2(const RpnProg *prog, q32_t *out_a, q32_t *out_b, q32_t *out_c) {
|
| 1306 |
+
int ok0, ok1, okm1, ok2, okm2, okt1, okt2;
|
| 1307 |
+
q32_t y0 = eval_rpn(prog, 0, &ok0, NULL, 0);
|
| 1308 |
+
q32_t y1 = eval_rpn(prog, Q32_ONE, &ok1, NULL, 0);
|
| 1309 |
+
q32_t ym1 = eval_rpn(prog, -Q32_ONE, &okm1, NULL, 0);
|
| 1310 |
+
if (!ok0 || !ok1 || !okm1) return -1;
|
| 1311 |
+
|
| 1312 |
+
q_err = 0;
|
| 1313 |
+
q32_t c = y0;
|
| 1314 |
+
q32_t b = q32_sub(y1, ym1) >> 1;
|
| 1315 |
+
q32_t a = q32_sub(q32_add(y1, ym1), q32_add(y0, y0)) >> 1;
|
| 1316 |
+
if (q_err) return -1;
|
| 1317 |
+
|
| 1318 |
+
q32_t xs[4] = {2 * Q32_ONE, -2 * Q32_ONE, (11LL << 29), -(13LL << 30)}; // 2, -2, 1.375, -3.25
|
| 1319 |
+
int *oks[4] = {&ok2, &okm2, &okt1, &okt2};
|
| 1320 |
+
for (int i = 0; i < 4; i++) {
|
| 1321 |
+
q32_t y = eval_rpn(prog, xs[i], oks[i], NULL, 0);
|
| 1322 |
+
if (!*oks[i]) return -1;
|
| 1323 |
+
q_err = 0;
|
| 1324 |
+
q32_t pred = q32_add(q32_mul(q32_add(q32_mul(a, xs[i]), b), xs[i]), c);
|
| 1325 |
+
if (q_err) return -1;
|
| 1326 |
+
q32_t diff = y > pred ? y - pred : pred - y;
|
| 1327 |
+
if (diff > 256) return -1; // > 6e-8 mismatch -> higher degree or transcendental
|
| 1328 |
+
}
|
| 1329 |
+
if (a > -64 && a < 64) a = 0;
|
| 1330 |
+
if (b > -64 && b < 64) b = 0;
|
| 1331 |
+
if (c > -64 && c < 64) c = 0;
|
| 1332 |
+
*out_a = a;
|
| 1333 |
+
*out_b = b;
|
| 1334 |
+
*out_c = c;
|
| 1335 |
+
return a != 0 ? 2 : (b != 0 ? 1 : 0);
|
| 1336 |
+
}
|
| 1337 |
+
|
| 1338 |
+
static q32_t bisect_diff(const RpnProg *lhs, const RpnProg *rhs, q32_t rhs_const,
|
| 1339 |
+
q32_t xa, q32_t xb, q32_t ga) {
|
| 1340 |
+
for (int it = 0; it < 28; it++) {
|
| 1341 |
+
q32_t xm = (xa >> 1) + (xb >> 1) + ((xa & xb) & 1LL);
|
| 1342 |
+
int ok1 = 1, ok2 = 1;
|
| 1343 |
+
q32_t lv = eval_rpn(lhs, xm, &ok1, NULL, 0);
|
| 1344 |
+
q32_t rv = (rhs && rhs->uses_x) ? eval_rpn(rhs, xm, &ok2, NULL, 0) : rhs_const;
|
| 1345 |
+
if (!ok1 || !ok2) break;
|
| 1346 |
+
q32_t gm = lv - rv;
|
| 1347 |
+
if (gm == 0) return xm;
|
| 1348 |
+
if ((ga < 0 && gm < 0) || (ga > 0 && gm > 0)) {
|
| 1349 |
+
xa = xm;
|
| 1350 |
+
ga = gm;
|
| 1351 |
+
} else {
|
| 1352 |
+
xb = xm;
|
| 1353 |
+
}
|
| 1354 |
+
}
|
| 1355 |
+
return (xa >> 1) + (xb >> 1);
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
static void add_unique_root(q32_t *roots, int *nroots, int cap, q32_t r) {
|
| 1359 |
+
for (int i = 0; i < *nroots; i++) {
|
| 1360 |
+
q32_t d = roots[i] > r ? roots[i] - r : r - roots[i];
|
| 1361 |
+
if (d < 400000LL) return; // < 1e-4 duplicate
|
| 1362 |
+
}
|
| 1363 |
+
if (*nroots < cap) {
|
| 1364 |
+
int pos = *nroots;
|
| 1365 |
+
while (pos > 0 && roots[pos - 1] > r) {
|
| 1366 |
+
roots[pos] = roots[pos - 1];
|
| 1367 |
+
pos--;
|
| 1368 |
+
}
|
| 1369 |
+
roots[pos] = r;
|
| 1370 |
+
(*nroots)++;
|
| 1371 |
+
}
|
| 1372 |
+
}
|
| 1373 |
+
|
| 1374 |
+
// --- Equation Solver Mode (`LHS = RHS`) ---
|
| 1375 |
+
static int run_solver_mode(const char *full_eq, const char *lhs_s, const char *rhs_s,
|
| 1376 |
+
int has_bounds, q32_t xmin, q32_t xmax) {
|
| 1377 |
+
RpnProg lhs, rhs;
|
| 1378 |
+
if (!compile_rpn(lhs_s, &lhs) || !compile_rpn(rhs_s, &rhs)) return 1;
|
| 1379 |
+
|
| 1380 |
+
puts_raw("equation: ");
|
| 1381 |
+
puts_raw(full_eq);
|
| 1382 |
+
rv_putchar('\n');
|
| 1383 |
+
|
| 1384 |
+
// Build combined difference program g(x) = LHS(x) - RHS(x) if small enough for poly detection
|
| 1385 |
+
RpnProg diff = lhs;
|
| 1386 |
+
int can_poly = (lhs.n + rhs.n + 1 <= 92);
|
| 1387 |
+
if (can_poly) {
|
| 1388 |
+
for (int i = 0; i < rhs.n; i++) diff.code[diff.n++] = rhs.code[i];
|
| 1389 |
+
diff.code[diff.n++] = (RpnInst){OP_SUB, 0};
|
| 1390 |
+
diff.uses_x = lhs.uses_x || rhs.uses_x;
|
| 1391 |
+
}
|
| 1392 |
+
|
| 1393 |
+
q32_t pa = 0, pb = 0, pc = 0;
|
| 1394 |
+
int deg = (!has_bounds && can_poly) ? detect_poly2(&diff, &pa, &pb, &pc) : -1;
|
| 1395 |
+
|
| 1396 |
+
char buf1[48], buf2[48];
|
| 1397 |
+
if (deg == 0) {
|
| 1398 |
+
puts_raw(pc == 0 ? "identity: true for all real x\n" : "no solution (contradiction)\n");
|
| 1399 |
+
return 0;
|
| 1400 |
+
}
|
| 1401 |
+
if (deg == 1) {
|
| 1402 |
+
q_err = 0;
|
| 1403 |
+
q32_t root = q32_div(-pc, pb);
|
| 1404 |
+
fmt_q32(pb, buf1, sizeof buf1, 6);
|
| 1405 |
+
fmt_q32(pc, buf2, sizeof buf2, 6);
|
| 1406 |
+
puts_raw("linear form: ");
|
| 1407 |
+
puts_raw(buf1);
|
| 1408 |
+
puts_raw("*x + (");
|
| 1409 |
+
puts_raw(buf2);
|
| 1410 |
+
puts_raw(") = 0\n");
|
| 1411 |
+
fmt_q32_rich(root, buf1, sizeof buf1);
|
| 1412 |
+
puts_raw("exact solution: x = ");
|
| 1413 |
+
puts_raw(buf1);
|
| 1414 |
+
rv_putchar('\n');
|
| 1415 |
+
int ok1, ok2;
|
| 1416 |
+
q32_t lv = eval_rpn(&lhs, root, &ok1, NULL, 0);
|
| 1417 |
+
q32_t rv = eval_rpn(&rhs, root, &ok2, NULL, 0);
|
| 1418 |
+
if (ok1 && ok2) {
|
| 1419 |
+
fmt_q32(lv, buf1, sizeof buf1, 6);
|
| 1420 |
+
fmt_q32(rv, buf2, sizeof buf2, 6);
|
| 1421 |
+
puts_raw("check: LHS = ");
|
| 1422 |
+
puts_raw(buf1);
|
| 1423 |
+
puts_raw(", RHS = ");
|
| 1424 |
+
puts_raw(buf2);
|
| 1425 |
+
rv_putchar('\n');
|
| 1426 |
+
}
|
| 1427 |
+
return 0;
|
| 1428 |
+
}
|
| 1429 |
+
if (deg == 2) {
|
| 1430 |
+
q_err = 0;
|
| 1431 |
+
q32_t disc = q32_sub(q32_mul(pb, pb), 4 * q32_mul(pa, pc));
|
| 1432 |
+
char ba[32], bb[32], bc[32], bd[32];
|
| 1433 |
+
fmt_q32(pa, ba, sizeof ba, 6);
|
| 1434 |
+
fmt_q32(pb, bb, sizeof bb, 6);
|
| 1435 |
+
fmt_q32(pc, bc, sizeof bc, 6);
|
| 1436 |
+
fmt_q32(disc, bd, sizeof bd, 6);
|
| 1437 |
+
puts_raw("quadratic form: (");
|
| 1438 |
+
puts_raw(ba);
|
| 1439 |
+
puts_raw(")*x^2 + (");
|
| 1440 |
+
puts_raw(bb);
|
| 1441 |
+
puts_raw(")*x + (");
|
| 1442 |
+
puts_raw(bc);
|
| 1443 |
+
puts_raw(") = 0\ndiscriminant: D = ");
|
| 1444 |
+
puts_raw(bd);
|
| 1445 |
+
rv_putchar('\n');
|
| 1446 |
+
q32_t two_a = 2 * pa;
|
| 1447 |
+
if (disc < -128) {
|
| 1448 |
+
q32_t re = q32_div(-pb, two_a);
|
| 1449 |
+
q32_t im = q32_div(q32_sqrt(-disc), two_a < 0 ? -two_a : two_a);
|
| 1450 |
+
fmt_q32(re, buf1, sizeof buf1, 6);
|
| 1451 |
+
fmt_q32(im, buf2, sizeof buf2, 6);
|
| 1452 |
+
puts_raw("real solutions: none (D < 0)\ncomplex solutions: x = ");
|
| 1453 |
+
puts_raw(buf1);
|
| 1454 |
+
puts_raw(" - ");
|
| 1455 |
+
puts_raw(buf2);
|
| 1456 |
+
puts_raw("*i, x = ");
|
| 1457 |
+
puts_raw(buf1);
|
| 1458 |
+
puts_raw(" + ");
|
| 1459 |
+
puts_raw(buf2);
|
| 1460 |
+
puts_raw("*i\n");
|
| 1461 |
+
return 0;
|
| 1462 |
+
}
|
| 1463 |
+
if (disc <= 128) {
|
| 1464 |
+
q32_t r = q32_div(-pb, two_a);
|
| 1465 |
+
fmt_q32_rich(r, buf1, sizeof buf1);
|
| 1466 |
+
puts_raw("double root: x = ");
|
| 1467 |
+
puts_raw(buf1);
|
| 1468 |
+
rv_putchar('\n');
|
| 1469 |
+
return 0;
|
| 1470 |
+
}
|
| 1471 |
+
q32_t sd = q32_sqrt(disc);
|
| 1472 |
+
q32_t r1 = q32_div(-pb - sd, two_a);
|
| 1473 |
+
q32_t r2 = q32_div(-pb + sd, two_a);
|
| 1474 |
+
if (r1 > r2) { q32_t t = r1; r1 = r2; r2 = t; }
|
| 1475 |
+
fmt_q32_rich(r1, buf1, sizeof buf1);
|
| 1476 |
+
fmt_q32_rich(r2, buf2, sizeof buf2);
|
| 1477 |
+
puts_raw("solutions: x = ");
|
| 1478 |
+
puts_raw(buf1);
|
| 1479 |
+
puts_raw(", x = ");
|
| 1480 |
+
puts_raw(buf2);
|
| 1481 |
+
rv_putchar('\n');
|
| 1482 |
+
int ok1, ok2;
|
| 1483 |
+
q32_t lv = eval_rpn(&lhs, r1, &ok1, NULL, 0);
|
| 1484 |
+
q32_t rv = eval_rpn(&rhs, r1, &ok2, NULL, 0);
|
| 1485 |
+
if (ok1 && ok2) {
|
| 1486 |
+
fmt_q32(lv, buf1, sizeof buf1, 6);
|
| 1487 |
+
fmt_q32(rv, buf2, sizeof buf2, 6);
|
| 1488 |
+
puts_raw("check (at x1): LHS = ");
|
| 1489 |
+
puts_raw(buf1);
|
| 1490 |
+
puts_raw(", RHS = ");
|
| 1491 |
+
puts_raw(buf2);
|
| 1492 |
+
rv_putchar('\n');
|
| 1493 |
+
}
|
| 1494 |
+
return 0;
|
| 1495 |
+
}
|
| 1496 |
+
|
| 1497 |
+
// General nonlinear equation scan + 28-step bisection
|
| 1498 |
+
q32_t lo = has_bounds ? xmin : -(20LL << 32);
|
| 1499 |
+
q32_t hi = has_bounds ? xmax : (20LL << 32);
|
| 1500 |
+
int rhs_const_ok = 1;
|
| 1501 |
+
q32_t rhs_const = rhs.uses_x ? 0 : eval_rpn(&rhs, 0, &rhs_const_ok, NULL, 0);
|
| 1502 |
+
q32_t roots[8];
|
| 1503 |
+
int nroots = 0;
|
| 1504 |
+
for (int pass = 0; pass < (has_bounds ? 1 : 2) && nroots == 0; pass++) {
|
| 1505 |
+
if (pass == 1) { lo = -(100LL << 32); hi = 100LL << 32; }
|
| 1506 |
+
const int N = 64;
|
| 1507 |
+
q32_t step = (hi - lo) >> 6;
|
| 1508 |
+
q32_t px = lo, pg = 0;
|
| 1509 |
+
int pok = 0;
|
| 1510 |
+
for (int i = 0; i <= N; i++) {
|
| 1511 |
+
q32_t x = (i == N) ? hi : (lo + i * step);
|
| 1512 |
+
int ok1, ok2 = rhs_const_ok;
|
| 1513 |
+
q32_t lv = eval_rpn(&lhs, x, &ok1, NULL, 0);
|
| 1514 |
+
q32_t rv = rhs.uses_x ? eval_rpn(&rhs, x, &ok2, NULL, 0) : rhs_const;
|
| 1515 |
+
int ok = ok1 && ok2;
|
| 1516 |
+
q32_t g = ok ? (lv - rv) : 0;
|
| 1517 |
+
if (ok && g > -256 && g < 256) {
|
| 1518 |
+
add_unique_root(roots, &nroots, 8, x);
|
| 1519 |
+
} else if (ok && pok && ((pg < 0 && g > 0) || (pg > 0 && g < 0))) {
|
| 1520 |
+
q32_t r = bisect_diff(&lhs, &rhs, rhs_const, px, x, pg);
|
| 1521 |
+
// Reject vertical asymptotes (e.g. 1/x = 0)
|
| 1522 |
+
int okc1, okc2 = rhs_const_ok;
|
| 1523 |
+
q32_t cl = eval_rpn(&lhs, r, &okc1, NULL, 0);
|
| 1524 |
+
q32_t cr = rhs.uses_x ? eval_rpn(&rhs, r, &okc2, NULL, 0) : rhs_const;
|
| 1525 |
+
q32_t rem = (okc1 && okc2) ? (cl > cr ? cl - cr : cr - cl) : Q32_ONE;
|
| 1526 |
+
if (okc1 && okc2 && rem < (Q32_ONE >> 10))
|
| 1527 |
+
add_unique_root(roots, &nroots, 8, r);
|
| 1528 |
+
}
|
| 1529 |
+
px = x; pg = g; pok = ok;
|
| 1530 |
+
}
|
| 1531 |
+
}
|
| 1532 |
+
if (nroots == 0) {
|
| 1533 |
+
puts_raw("solutions on search interval: none found\n");
|
| 1534 |
+
} else {
|
| 1535 |
+
puts_raw("solutions: ");
|
| 1536 |
+
for (int i = 0; i < nroots; i++) {
|
| 1537 |
+
if (i > 0) puts_raw(", ");
|
| 1538 |
+
puts_raw("x = ");
|
| 1539 |
+
fmt_q32_rich(roots[i], buf1, sizeof buf1);
|
| 1540 |
+
puts_raw(buf1);
|
| 1541 |
+
}
|
| 1542 |
+
rv_putchar('\n');
|
| 1543 |
+
int ok1, ok2;
|
| 1544 |
+
q32_t lv = eval_rpn(&lhs, roots[0], &ok1, NULL, 0);
|
| 1545 |
+
q32_t rv = eval_rpn(&rhs, roots[0], &ok2, NULL, 0);
|
| 1546 |
+
if (ok1 && ok2) {
|
| 1547 |
+
fmt_q32(lv, buf1, sizeof buf1, 6);
|
| 1548 |
+
fmt_q32(rv, buf2, sizeof buf2, 6);
|
| 1549 |
+
puts_raw("check: LHS = ");
|
| 1550 |
+
puts_raw(buf1);
|
| 1551 |
+
puts_raw(", RHS = ");
|
| 1552 |
+
puts_raw(buf2);
|
| 1553 |
+
rv_putchar('\n');
|
| 1554 |
+
}
|
| 1555 |
+
}
|
| 1556 |
+
return 0;
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
// Golden-section polish for local extremum on [a, b] (`find_max` = 1 for max, 0 for min)
|
| 1560 |
+
static q32_t polish_extremum(const RpnProg *prog, q32_t a, q32_t b, int find_max, q32_t *out_y) {
|
| 1561 |
+
// inv_phi = (sqrt(5) - 1) / 2 = 0.61803398875 in Q32.32 = 2654435769LL
|
| 1562 |
+
const q32_t INV_PHI = 2654435769LL;
|
| 1563 |
+
q32_t c = b - q32_mul(b - a, INV_PHI);
|
| 1564 |
+
q32_t d = a + q32_mul(b - a, INV_PHI);
|
| 1565 |
+
int okc, okd;
|
| 1566 |
+
q32_t yc = eval_rpn(prog, c, &okc, NULL, 0);
|
| 1567 |
+
q32_t yd = eval_rpn(prog, d, &okd, NULL, 0);
|
| 1568 |
+
for (int it = 0; it < 15 && okc && okd; it++) {
|
| 1569 |
+
int pick_left = find_max ? (yc > yd) : (yc < yd);
|
| 1570 |
+
if (pick_left) {
|
| 1571 |
+
b = d; d = c; yd = yc;
|
| 1572 |
+
c = b - q32_mul(b - a, INV_PHI);
|
| 1573 |
+
yc = eval_rpn(prog, c, &okc, NULL, 0);
|
| 1574 |
+
} else {
|
| 1575 |
+
a = c; c = d; yc = yd;
|
| 1576 |
+
d = a + q32_mul(b - a, INV_PHI);
|
| 1577 |
+
yd = eval_rpn(prog, d, &okd, NULL, 0);
|
| 1578 |
+
}
|
| 1579 |
+
}
|
| 1580 |
+
q32_t xm = (a >> 1) + (b >> 1);
|
| 1581 |
+
int okm, okp, okn;
|
| 1582 |
+
q32_t ym = eval_rpn(prog, xm, &okm, NULL, 0);
|
| 1583 |
+
// Final 3-point parabolic vertex polish (`dh = 2^-11`) for exact 6-decimal extrema
|
| 1584 |
+
q32_t dh = (1LL << 21);
|
| 1585 |
+
q32_t yp = eval_rpn(prog, xm + dh, &okp, NULL, 0);
|
| 1586 |
+
q32_t yn = eval_rpn(prog, xm - dh, &okn, NULL, 0);
|
| 1587 |
+
if (okm && okp && okn) {
|
| 1588 |
+
q32_t denom = 2 * ((yp - 2 * ym) + yn);
|
| 1589 |
+
if (denom > 1024 || denom < -1024) {
|
| 1590 |
+
int save = q_err;
|
| 1591 |
+
q_err = 0;
|
| 1592 |
+
q32_t delta = q32_mul(dh, q32_div(yn - yp, denom));
|
| 1593 |
+
if (!q_err && delta > -dh && delta < dh) {
|
| 1594 |
+
xm += delta;
|
| 1595 |
+
ym = eval_rpn(prog, xm, &okm, NULL, 0);
|
| 1596 |
+
}
|
| 1597 |
+
q_err = save;
|
| 1598 |
+
}
|
| 1599 |
+
}
|
| 1600 |
+
*out_y = ym;
|
| 1601 |
+
return xm;
|
| 1602 |
+
}
|
| 1603 |
+
|
| 1604 |
+
// --- Graphing Calculator & Table Mode (`y = f(x)`) ---
|
| 1605 |
+
static int run_graph_mode(const char *expr_s, const RpnProg *prog, q32_t xmin, q32_t xmax, int table_only) {
|
| 1606 |
+
if (xmax <= xmin) xmax = xmin + (10LL << 32);
|
| 1607 |
+
|
| 1608 |
+
// 48 subintervals -> 49 grid points:
|
| 1609 |
+
// 48 = 2 * 24 (exact 25 plot columns at i = 2*col)
|
| 1610 |
+
// 48 = 6 * 8 (exact 7 table points at i = 8*k)
|
| 1611 |
+
// 48 is even (exact 48-panel Simpson's rule definite integral!)
|
| 1612 |
+
static q32_t gx[49], gy[49];
|
| 1613 |
+
static uint8_t gok[49];
|
| 1614 |
+
q32_t span = xmax - xmin;
|
| 1615 |
+
q32_t h = q32_div(span, 48 * Q32_ONE);
|
| 1616 |
+
|
| 1617 |
+
q32_t ymin = Q32_MAX, ymax = Q32_MIN;
|
| 1618 |
+
int valid_cnt = 0, all_valid = 1;
|
| 1619 |
+
for (int i = 0; i <= 48; i++) {
|
| 1620 |
+
gx[i] = (i == 48) ? xmax : (xmin + i * h);
|
| 1621 |
+
int ok = 0;
|
| 1622 |
+
gy[i] = eval_rpn(prog, gx[i], &ok, NULL, 0);
|
| 1623 |
+
gok[i] = (uint8_t)ok;
|
| 1624 |
+
if (ok) {
|
| 1625 |
+
if (gy[i] < ymin) ymin = gy[i];
|
| 1626 |
+
if (gy[i] > ymax) ymax = gy[i];
|
| 1627 |
+
valid_cnt++;
|
| 1628 |
+
} else {
|
| 1629 |
+
all_valid = 0;
|
| 1630 |
+
}
|
| 1631 |
+
}
|
| 1632 |
+
|
| 1633 |
+
char b1[40], b2[40], b3[40];
|
| 1634 |
+
fmt_q32(xmin, b1, sizeof b1, 4);
|
| 1635 |
+
fmt_q32(xmax, b2, sizeof b2, 4);
|
| 1636 |
+
puts_raw("function: f(x) = ");
|
| 1637 |
+
puts_raw(expr_s);
|
| 1638 |
+
puts_raw(" on [");
|
| 1639 |
+
puts_raw(b1);
|
| 1640 |
+
puts_raw(", ");
|
| 1641 |
+
puts_raw(b2);
|
| 1642 |
+
puts_raw("]\n");
|
| 1643 |
+
|
| 1644 |
+
if (valid_cnt == 0) {
|
| 1645 |
+
puts_raw("error: function undefined on entire interval\n");
|
| 1646 |
+
return 0;
|
| 1647 |
+
}
|
| 1648 |
+
|
| 1649 |
+
// 1. Roots on [xmin, xmax]
|
| 1650 |
+
q32_t roots[8];
|
| 1651 |
+
int nroots = 0;
|
| 1652 |
+
q32_t pa = 0, pb = 0, pc = 0;
|
| 1653 |
+
int deg = detect_poly2(prog, &pa, &pb, &pc);
|
| 1654 |
+
if (deg == 1) {
|
| 1655 |
+
q32_t r = q32_div(-pc, pb);
|
| 1656 |
+
if (r >= xmin - 1024 && r <= xmax + 1024) add_unique_root(roots, &nroots, 8, r);
|
| 1657 |
+
} else if (deg == 2) {
|
| 1658 |
+
q32_t disc = q32_sub(q32_mul(pb, pb), 4 * q32_mul(pa, pc));
|
| 1659 |
+
if (disc >= -128) {
|
| 1660 |
+
q32_t sd = disc > 0 ? q32_sqrt(disc) : 0;
|
| 1661 |
+
q32_t r1 = q32_div(-pb - sd, 2 * pa);
|
| 1662 |
+
q32_t r2 = q32_div(-pb + sd, 2 * pa);
|
| 1663 |
+
if (r1 >= xmin - 1024 && r1 <= xmax + 1024) add_unique_root(roots, &nroots, 8, r1);
|
| 1664 |
+
if (r2 >= xmin - 1024 && r2 <= xmax + 1024) add_unique_root(roots, &nroots, 8, r2);
|
| 1665 |
+
}
|
| 1666 |
+
} else {
|
| 1667 |
+
for (int i = 0; i <= 48; i++) {
|
| 1668 |
+
if (gok[i] && gy[i] > -512 && gy[i] < 512) {
|
| 1669 |
+
add_unique_root(roots, &nroots, 8, gx[i]);
|
| 1670 |
+
} else if (i < 48 && gok[i] && gok[i + 1] &&
|
| 1671 |
+
((gy[i] < 0 && gy[i + 1] > 0) || (gy[i] > 0 && gy[i + 1] < 0))) {
|
| 1672 |
+
q32_t r = bisect_diff(prog, NULL, 0, gx[i], gx[i + 1], gy[i]);
|
| 1673 |
+
int okr;
|
| 1674 |
+
q32_t yr = eval_rpn(prog, r, &okr, NULL, 0);
|
| 1675 |
+
q32_t ay = yr < 0 ? -yr : yr;
|
| 1676 |
+
if (okr && ay < (Q32_ONE >> 10))
|
| 1677 |
+
add_unique_root(roots, &nroots, 8, r);
|
| 1678 |
+
}
|
| 1679 |
+
}
|
| 1680 |
+
}
|
| 1681 |
+
puts_raw("roots: ");
|
| 1682 |
+
if (nroots == 0) {
|
| 1683 |
+
puts_raw("none on interval\n");
|
| 1684 |
+
} else {
|
| 1685 |
+
for (int i = 0; i < nroots; i++) {
|
| 1686 |
+
if (i > 0) puts_raw(", ");
|
| 1687 |
+
puts_raw("x = ");
|
| 1688 |
+
fmt_q32_rich(roots[i], b1, sizeof b1);
|
| 1689 |
+
puts_raw(b1);
|
| 1690 |
+
}
|
| 1691 |
+
rv_putchar('\n');
|
| 1692 |
+
}
|
| 1693 |
+
|
| 1694 |
+
// 2. Vertex / Local Extrema
|
| 1695 |
+
if (deg == 2) {
|
| 1696 |
+
q32_t vx = q32_div(-pb, 2 * pa);
|
| 1697 |
+
int okv;
|
| 1698 |
+
q32_t vy = eval_rpn(prog, vx, &okv, NULL, 0);
|
| 1699 |
+
if (okv && vy < ymin && vx >= xmin && vx <= xmax) ymin = vy;
|
| 1700 |
+
if (okv && vy > ymax && vx >= xmin && vx <= xmax) ymax = vy;
|
| 1701 |
+
fmt_q32(vx, b1, sizeof b1, 6);
|
| 1702 |
+
fmt_q32(vy, b2, sizeof b2, 6);
|
| 1703 |
+
puts_raw(pa > 0 ? "vertex (min): (" : "vertex (max): (");
|
| 1704 |
+
puts_raw(b1);
|
| 1705 |
+
puts_raw(", ");
|
| 1706 |
+
puts_raw(b2);
|
| 1707 |
+
puts_raw(")\n");
|
| 1708 |
+
} else {
|
| 1709 |
+
int printed_ext = 0;
|
| 1710 |
+
for (int i = 1; i < 48 && printed_ext < 3; i++) {
|
| 1711 |
+
if (!gok[i - 1] || !gok[i] || !gok[i + 1]) continue;
|
| 1712 |
+
int is_max = (gy[i] > gy[i - 1] && gy[i] >= gy[i + 1] && (gy[i] > gy[i - 1] || gy[i] > gy[i + 1]));
|
| 1713 |
+
int is_min = (gy[i] < gy[i - 1] && gy[i] <= gy[i + 1] && (gy[i] < gy[i - 1] || gy[i] < gy[i + 1]));
|
| 1714 |
+
if (is_max || is_min) {
|
| 1715 |
+
q32_t ey = 0;
|
| 1716 |
+
q32_t ex = polish_extremum(prog, gx[i - 1], gx[i + 1], is_max, &ey);
|
| 1717 |
+
if (ey < ymin) ymin = ey;
|
| 1718 |
+
if (ey > ymax) ymax = ey;
|
| 1719 |
+
if (printed_ext > 0) puts_raw(" | ");
|
| 1720 |
+
puts_raw(is_max ? "local max: (" : "local min: (");
|
| 1721 |
+
fmt_q32(ex, b1, sizeof b1, 6);
|
| 1722 |
+
fmt_q32(ey, b2, sizeof b2, 6);
|
| 1723 |
+
puts_raw(b1);
|
| 1724 |
+
puts_raw(", ");
|
| 1725 |
+
puts_raw(b2);
|
| 1726 |
+
rv_putchar(')');
|
| 1727 |
+
printed_ext++;
|
| 1728 |
+
}
|
| 1729 |
+
}
|
| 1730 |
+
if (printed_ext > 0) rv_putchar('\n');
|
| 1731 |
+
}
|
| 1732 |
+
|
| 1733 |
+
// 3. y-intercept & 96-panel Simpson's definite integral
|
| 1734 |
+
int has_yint = 0;
|
| 1735 |
+
if (xmin <= 0 && xmax >= 0) {
|
| 1736 |
+
int ok0;
|
| 1737 |
+
q32_t y0 = eval_rpn(prog, 0, &ok0, NULL, 0);
|
| 1738 |
+
if (ok0) {
|
| 1739 |
+
fmt_q32(y0, b1, sizeof b1, 6);
|
| 1740 |
+
puts_raw("y-intercept: (0, ");
|
| 1741 |
+
puts_raw(b1);
|
| 1742 |
+
rv_putchar(')');
|
| 1743 |
+
has_yint = 1;
|
| 1744 |
+
}
|
| 1745 |
+
}
|
| 1746 |
+
if (all_valid) {
|
| 1747 |
+
q_err = 0;
|
| 1748 |
+
q32_t sum = q32_add(gy[0], gy[48]);
|
| 1749 |
+
for (int i = 1; i < 48 && !q_err; i++) {
|
| 1750 |
+
q32_t term = (i & 1) ? q32_mul(4 * Q32_ONE, gy[i]) : q32_mul(2 * Q32_ONE, gy[i]);
|
| 1751 |
+
sum = q32_add(sum, term);
|
| 1752 |
+
}
|
| 1753 |
+
q32_t integ = q32_mul(q32_div(h, 3 * Q32_ONE), sum);
|
| 1754 |
+
if (!q_err) {
|
| 1755 |
+
if (has_yint) puts_raw(" | ");
|
| 1756 |
+
puts_raw("integral: ");
|
| 1757 |
+
fmt_q32(integ, b1, sizeof b1, 6);
|
| 1758 |
+
puts_raw(b1);
|
| 1759 |
+
has_yint = 1;
|
| 1760 |
+
}
|
| 1761 |
+
}
|
| 1762 |
+
if (has_yint) rv_putchar('\n');
|
| 1763 |
+
|
| 1764 |
+
// 4. Coordinate Table (7 clean points; with derivative f'(x) when in table mode)
|
| 1765 |
+
if (table_only) {
|
| 1766 |
+
puts_raw("x | f(x) | f'(x)\n");
|
| 1767 |
+
q32_t dh = (1LL << 20); // 2^-12 (~2.44e-4) central difference step
|
| 1768 |
+
for (int k = 0; k <= 6; k++) {
|
| 1769 |
+
int idx = 8 * k;
|
| 1770 |
+
fmt_q32(gx[idx], b1, sizeof b1, 4);
|
| 1771 |
+
puts_raw(b1);
|
| 1772 |
+
puts_raw(" | ");
|
| 1773 |
+
if (!gok[idx]) {
|
| 1774 |
+
puts_raw("undef | undef\n");
|
| 1775 |
+
} else {
|
| 1776 |
+
fmt_q32(gy[idx], b2, sizeof b2, 6);
|
| 1777 |
+
puts_raw(b2);
|
| 1778 |
+
puts_raw(" | ");
|
| 1779 |
+
int okp, okm;
|
| 1780 |
+
q32_t yp = eval_rpn(prog, gx[idx] + dh, &okp, NULL, 0);
|
| 1781 |
+
q32_t ym = eval_rpn(prog, gx[idx] - dh, &okm, NULL, 0);
|
| 1782 |
+
if (okp && okm) {
|
| 1783 |
+
q32_t deriv = (yp - ym) << 11; // / (2 * 2^-12) = * 2^11
|
| 1784 |
+
fmt_q32(deriv, b3, sizeof b3, 6);
|
| 1785 |
+
puts_raw(b3);
|
| 1786 |
+
} else {
|
| 1787 |
+
puts_raw("undef");
|
| 1788 |
+
}
|
| 1789 |
+
rv_putchar('\n');
|
| 1790 |
+
}
|
| 1791 |
+
}
|
| 1792 |
+
return 0;
|
| 1793 |
+
}
|
| 1794 |
+
|
| 1795 |
+
puts_raw("table: ");
|
| 1796 |
+
for (int k = 0; k <= 6; k++) {
|
| 1797 |
+
int idx = 8 * k;
|
| 1798 |
+
if (k > 0) puts_raw(", ");
|
| 1799 |
+
rv_putchar('(');
|
| 1800 |
+
fmt_q32(gx[idx], b1, sizeof b1, 4);
|
| 1801 |
+
puts_raw(b1);
|
| 1802 |
+
puts_raw(", ");
|
| 1803 |
+
if (gok[idx]) {
|
| 1804 |
+
fmt_q32(gy[idx], b2, sizeof b2, 4);
|
| 1805 |
+
puts_raw(b2);
|
| 1806 |
+
} else {
|
| 1807 |
+
puts_raw("undef");
|
| 1808 |
+
}
|
| 1809 |
+
rv_putchar(')');
|
| 1810 |
+
}
|
| 1811 |
+
rv_putchar('\n');
|
| 1812 |
+
|
| 1813 |
+
// 5. 9x25 ASCII Coordinate Plot
|
| 1814 |
+
q32_t pmin = ymin, pmax = ymax;
|
| 1815 |
+
// Clamp extreme asymptote spikes so e.g. 1/x or tan(x) remains readable
|
| 1816 |
+
if (pmax - pmin > (200LL << 32) && !all_valid) {
|
| 1817 |
+
pmin = -(20LL << 32);
|
| 1818 |
+
pmax = 20LL << 32;
|
| 1819 |
+
}
|
| 1820 |
+
if (pmax - pmin < (1LL << 28)) {
|
| 1821 |
+
pmin -= Q32_ONE;
|
| 1822 |
+
pmax += Q32_ONE;
|
| 1823 |
+
}
|
| 1824 |
+
q32_t yspan = pmax - pmin;
|
| 1825 |
+
|
| 1826 |
+
char grid[9][25];
|
| 1827 |
+
for (int r = 0; r < 9; r++)
|
| 1828 |
+
for (int c = 0; c < 25; c++) grid[r][c] = ' ';
|
| 1829 |
+
|
| 1830 |
+
int rx = -1, cy = -1;
|
| 1831 |
+
q32_t inv_yspan8 = q32_div(8 * Q32_ONE, yspan);
|
| 1832 |
+
if (pmin <= 0 && pmax >= 0) {
|
| 1833 |
+
rx = (int)((q32_mul(pmax, inv_yspan8) + (1LL << 31)) >> 32);
|
| 1834 |
+
if (rx >= 0 && rx < 9)
|
| 1835 |
+
for (int c = 0; c < 25; c++) grid[rx][c] = '-';
|
| 1836 |
+
}
|
| 1837 |
+
if (xmin <= 0 && xmax >= 0) {
|
| 1838 |
+
cy = (int)((q32_div(-xmin, span) * 24 + (1LL << 31)) >> 32);
|
| 1839 |
+
if (cy >= 0 && cy < 25)
|
| 1840 |
+
for (int r = 0; r < 9; r++) grid[r][cy] = '|';
|
| 1841 |
+
}
|
| 1842 |
+
if (rx >= 0 && rx < 9 && cy >= 0 && cy < 25) grid[rx][cy] = '+';
|
| 1843 |
+
|
| 1844 |
+
for (int c = 0; c < 25; c++) {
|
| 1845 |
+
int idx = 2 * c;
|
| 1846 |
+
if (!gok[idx] || gy[idx] < pmin || gy[idx] > pmax) continue;
|
| 1847 |
+
int r = (int)((q32_mul(pmax - gy[idx], inv_yspan8) + (1LL << 31)) >> 32);
|
| 1848 |
+
if (r >= 0 && r < 9) grid[r][c] = '*';
|
| 1849 |
+
}
|
| 1850 |
+
|
| 1851 |
+
for (int r = 0; r < 9; r++) {
|
| 1852 |
+
if ((r & 1) == 0 || r == rx) {
|
| 1853 |
+
q32_t ytick = (r == rx) ? 0 : (pmax - (yspan * r) / 8);
|
| 1854 |
+
fmt_q32(ytick, b1, sizeof b1, 2);
|
| 1855 |
+
int l = slen(b1);
|
| 1856 |
+
putc_rep(' ', l < 7 ? 7 - l : 0);
|
| 1857 |
+
puts_raw(b1);
|
| 1858 |
+
} else {
|
| 1859 |
+
putc_rep(' ', 7);
|
| 1860 |
+
}
|
| 1861 |
+
puts_raw(" |");
|
| 1862 |
+
int endc = 25;
|
| 1863 |
+
while (endc > 0 && grid[r][endc - 1] == ' ') endc--;
|
| 1864 |
+
for (int c = 0; c < endc; c++) rv_putchar(grid[r][c]);
|
| 1865 |
+
rv_putchar('\n');
|
| 1866 |
+
}
|
| 1867 |
+
// Bottom x-axis tick line
|
| 1868 |
+
fmt_q32(xmin, b1, sizeof b1, 2);
|
| 1869 |
+
fmt_q32((xmin >> 1) + (xmax >> 1), b2, sizeof b2, 2);
|
| 1870 |
+
fmt_q32(xmax, b3, sizeof b3, 2);
|
| 1871 |
+
puts_raw(" x=");
|
| 1872 |
+
puts_raw(b1);
|
| 1873 |
+
int used_w = 2 + slen(b1);
|
| 1874 |
+
int mid_target = 11 - (2 + slen(b2)) / 2;
|
| 1875 |
+
if (mid_target <= used_w) mid_target = used_w + 1;
|
| 1876 |
+
putc_rep(' ', mid_target - used_w);
|
| 1877 |
+
puts_raw("x=");
|
| 1878 |
+
puts_raw(b2);
|
| 1879 |
+
used_w = mid_target + 2 + slen(b2);
|
| 1880 |
+
int right_target = 25 - (2 + slen(b3));
|
| 1881 |
+
if (right_target <= used_w) right_target = used_w + 1;
|
| 1882 |
+
putc_rep(' ', right_target - used_w);
|
| 1883 |
+
puts_raw("x=");
|
| 1884 |
+
puts_raw(b3);
|
| 1885 |
+
rv_putchar('\n');
|
| 1886 |
+
return 0;
|
| 1887 |
+
}
|
| 1888 |
+
|
| 1889 |
+
// ============================================================================
|
| 1890 |
+
// Tool-Call Gathering, UTF-8 Normalization & Mode Dispatcher
|
| 1891 |
+
// ============================================================================
|
| 1892 |
+
|
| 1893 |
+
static char raw_call[1024];
|
| 1894 |
+
static char norm_call[1024];
|
| 1895 |
+
static char fn_buf[32];
|
| 1896 |
+
static char expr_buf[256];
|
| 1897 |
+
static char xmin_buf[64];
|
| 1898 |
+
static char xmax_buf[64];
|
| 1899 |
+
static int has_xmin, has_xmax;
|
| 1900 |
+
|
| 1901 |
+
static void normalize_utf8(const char *src, char *dst, int cap) {
|
| 1902 |
+
int o = 0;
|
| 1903 |
+
for (int i = 0; src[i] && o < cap - 4; ) {
|
| 1904 |
+
unsigned char c0 = (unsigned char)src[i];
|
| 1905 |
+
unsigned char c1 = (unsigned char)src[i + 1];
|
| 1906 |
+
unsigned char c2 = c1 ? (unsigned char)src[i + 2] : 0;
|
| 1907 |
+
if (c0 == 0xC3 && c1 == 0x97) { dst[o++] = '*'; i += 2; } // ×
|
| 1908 |
+
else if (c0 == 0xC3 && c1 == 0xB7) { dst[o++] = '/'; i += 2; } // ÷
|
| 1909 |
+
else if (c0 == 0xC2 && c1 == 0xB2) { dst[o++] = '^'; dst[o++] = '2'; i += 2; } // ²
|
| 1910 |
+
else if (c0 == 0xC2 && c1 == 0xB3) { dst[o++] = '^'; dst[o++] = '3'; i += 2; } // ³
|
| 1911 |
+
else if (c0 == 0xCF && c1 == 0x80) { dst[o++] = 'p'; dst[o++] = 'i'; i += 2; } // π
|
| 1912 |
+
else if (c0 == 0xE2 && c1 == 0x88 && c2 == 0x92) { dst[o++] = '-'; i += 3; } // −
|
| 1913 |
+
else if (c0 == 0xE2 && c1 == 0x80 && c2 == 0x93) { dst[o++] = '-'; i += 3; } // –
|
| 1914 |
+
else if (c0 == '&' && starts_with_ci(src + i, "<")) { dst[o++] = '<'; i += 4; }
|
| 1915 |
+
else if (c0 == '&' && starts_with_ci(src + i, ">")) { dst[o++] = '>'; i += 4; }
|
| 1916 |
+
else { dst[o++] = src[i++]; }
|
| 1917 |
+
}
|
| 1918 |
+
dst[o] = 0;
|
| 1919 |
+
}
|
| 1920 |
+
|
| 1921 |
+
static void parse_call_payload(void) {
|
| 1922 |
+
normalize_utf8(raw_call, norm_call, sizeof norm_call);
|
| 1923 |
+
fn_buf[0] = 0;
|
| 1924 |
+
expr_buf[0] = 0;
|
| 1925 |
+
xmin_buf[0] = 0;
|
| 1926 |
+
xmax_buf[0] = 0;
|
| 1927 |
+
has_xmin = 0;
|
| 1928 |
+
has_xmax = 0;
|
| 1929 |
+
|
| 1930 |
+
int saw_param = 0;
|
| 1931 |
+
for (int i = 0; norm_call[i]; i++) {
|
| 1932 |
+
if (starts_with_ci(norm_call + i, "<function=")) {
|
| 1933 |
+
int s = i + 10, e = s;
|
| 1934 |
+
while (norm_call[e] && norm_call[e] != '>' && !is_space(norm_call[e])) e++;
|
| 1935 |
+
copy_trim(fn_buf, sizeof fn_buf, norm_call + s, e - s);
|
| 1936 |
+
} else if (starts_with_ci(norm_call + i, "<parameter=")) {
|
| 1937 |
+
saw_param = 1;
|
| 1938 |
+
int ks = i + 11, ke = ks;
|
| 1939 |
+
while (norm_call[ke] && norm_call[ke] != '>') ke++;
|
| 1940 |
+
if (!norm_call[ke]) break;
|
| 1941 |
+
char key[24];
|
| 1942 |
+
copy_trim(key, sizeof key, norm_call + ks, ke - ks);
|
| 1943 |
+
for (int k = 0; key[k]; k++) if (key[k] >= 'A' && key[k] <= 'Z') key[k] = (char)(key[k] + 32);
|
| 1944 |
+
int vs = ke + 1, ve = vs;
|
| 1945 |
+
while (norm_call[ve] && norm_call[ve] != '<') ve++;
|
| 1946 |
+
if (starts_with_ci(key, "expr") || starts_with_ci(key, "eq") ||
|
| 1947 |
+
starts_with_ci(key, "func") || starts_with_ci(key, "cmd") || streq(key, "f")) {
|
| 1948 |
+
copy_trim(expr_buf, sizeof expr_buf, norm_call + vs, ve - vs);
|
| 1949 |
+
} else if (streq(key, "xmin") || streq(key, "min") || streq(key, "a") || streq(key, "from")) {
|
| 1950 |
+
copy_trim(xmin_buf, sizeof xmin_buf, norm_call + vs, ve - vs);
|
| 1951 |
+
has_xmin = xmin_buf[0] != 0;
|
| 1952 |
+
} else if (streq(key, "xmax") || streq(key, "max") || streq(key, "b") || streq(key, "to")) {
|
| 1953 |
+
copy_trim(xmax_buf, sizeof xmax_buf, norm_call + vs, ve - vs);
|
| 1954 |
+
has_xmax = xmax_buf[0] != 0;
|
| 1955 |
+
} else if (streq(key, "mode")) {
|
| 1956 |
+
copy_trim(fn_buf, sizeof fn_buf, norm_call + vs, ve - vs);
|
| 1957 |
+
} else if (!expr_buf[0]) {
|
| 1958 |
+
copy_trim(expr_buf, sizeof expr_buf, norm_call + vs, ve - vs);
|
| 1959 |
+
}
|
| 1960 |
+
i = ve - 1;
|
| 1961 |
+
}
|
| 1962 |
+
}
|
| 1963 |
+
|
| 1964 |
+
if (!saw_param) {
|
| 1965 |
+
// Fallback: JSON {"expression": "..."} or plain text expression
|
| 1966 |
+
int found_json = 0;
|
| 1967 |
+
for (int i = 0; norm_call[i]; i++) {
|
| 1968 |
+
if (starts_with_ci(norm_call + i, "\"expression\"") || starts_with_ci(norm_call + i, "\"equation\"")) {
|
| 1969 |
+
int j = i + 12;
|
| 1970 |
+
while (norm_call[j] && norm_call[j] != ':') j++;
|
| 1971 |
+
if (norm_call[j] == ':') j++;
|
| 1972 |
+
while (is_space(norm_call[j])) j++;
|
| 1973 |
+
if (norm_call[j] == '"') {
|
| 1974 |
+
int vs = ++j;
|
| 1975 |
+
while (norm_call[j] && norm_call[j] != '"') j++;
|
| 1976 |
+
copy_trim(expr_buf, sizeof expr_buf, norm_call + vs, j - vs);
|
| 1977 |
+
found_json = 1;
|
| 1978 |
+
break;
|
| 1979 |
+
}
|
| 1980 |
+
}
|
| 1981 |
+
}
|
| 1982 |
+
if (!found_json) copy_trim(expr_buf, sizeof expr_buf, norm_call, slen(norm_call));
|
| 1983 |
+
}
|
| 1984 |
+
|
| 1985 |
+
// Strip optional inline domain suffix: " on [a, b]" or " from x = a to b" or " from a to b"
|
| 1986 |
+
int n = slen(expr_buf);
|
| 1987 |
+
for (int i = 0; i < n; i++) {
|
| 1988 |
+
if (!has_xmin && (starts_with_ci(expr_buf + i, " on [") || starts_with_ci(expr_buf + i, " in ["))) {
|
| 1989 |
+
int lb = i + 5, cm = lb;
|
| 1990 |
+
while (expr_buf[cm] && expr_buf[cm] != ',' && expr_buf[cm] != ']') cm++;
|
| 1991 |
+
if (expr_buf[cm] == ',') {
|
| 1992 |
+
int rb = cm + 1;
|
| 1993 |
+
while (expr_buf[rb] && expr_buf[rb] != ']') rb++;
|
| 1994 |
+
if (expr_buf[rb] == ']') {
|
| 1995 |
+
copy_trim(xmin_buf, sizeof xmin_buf, expr_buf + lb, cm - lb);
|
| 1996 |
+
copy_trim(xmax_buf, sizeof xmax_buf, expr_buf + cm + 1, rb - cm - 1);
|
| 1997 |
+
has_xmin = has_xmax = 1;
|
| 1998 |
+
expr_buf[i] = 0;
|
| 1999 |
+
copy_trim(expr_buf, sizeof expr_buf, expr_buf, i);
|
| 2000 |
+
break;
|
| 2001 |
+
}
|
| 2002 |
+
}
|
| 2003 |
+
} else if (!has_xmin && starts_with_ci(expr_buf + i, " from ")) {
|
| 2004 |
+
int p = i + 6;
|
| 2005 |
+
while (is_space(expr_buf[p])) p++;
|
| 2006 |
+
if ((expr_buf[p] == 'x' || expr_buf[p] == 'X') && (expr_buf[p + 1] == '=' || expr_buf[p + 1] == ' ')) {
|
| 2007 |
+
p++;
|
| 2008 |
+
while (is_space(expr_buf[p]) || expr_buf[p] == '=') p++;
|
| 2009 |
+
}
|
| 2010 |
+
int to_pos = -1;
|
| 2011 |
+
for (int k = p; expr_buf[k]; k++) {
|
| 2012 |
+
if (starts_with_ci(expr_buf + k, " to ")) { to_pos = k; break; }
|
| 2013 |
+
}
|
| 2014 |
+
if (to_pos > p) {
|
| 2015 |
+
copy_trim(xmin_buf, sizeof xmin_buf, expr_buf + p, to_pos - p);
|
| 2016 |
+
copy_trim(xmax_buf, sizeof xmax_buf, expr_buf + to_pos + 4, slen(expr_buf) - (to_pos + 4));
|
| 2017 |
+
has_xmin = has_xmax = 1;
|
| 2018 |
+
expr_buf[i] = 0;
|
| 2019 |
+
copy_trim(expr_buf, sizeof expr_buf, expr_buf, i);
|
| 2020 |
+
break;
|
| 2021 |
+
}
|
| 2022 |
+
}
|
| 2023 |
+
}
|
| 2024 |
+
|
| 2025 |
+
// Optional command prefix ("graph ...", "plot ...", "solve ...", "table ...")
|
| 2026 |
+
const char *prefixes[4] = {"graph ", "plot ", "solve ", "table "};
|
| 2027 |
+
for (int k = 0; k < 4; k++) {
|
| 2028 |
+
if (starts_with_ci(expr_buf, prefixes[k])) {
|
| 2029 |
+
int plen = slen(prefixes[k]);
|
| 2030 |
+
if (!fn_buf[0] || streq(fn_buf, "graphcalc"))
|
| 2031 |
+
copy_trim(fn_buf, sizeof fn_buf, prefixes[k], plen - 1);
|
| 2032 |
+
copy_trim(expr_buf, sizeof expr_buf, expr_buf + plen, slen(expr_buf) - plen);
|
| 2033 |
+
break;
|
| 2034 |
+
}
|
| 2035 |
+
}
|
| 2036 |
+
|
| 2037 |
+
// Strip leading "y =" or "f(x) =" when there is no second '='
|
| 2038 |
+
int eq_cnt = 0, first_eq = -1;
|
| 2039 |
+
for (int i = 0; expr_buf[i]; i++) {
|
| 2040 |
+
if (expr_buf[i] == '=') {
|
| 2041 |
+
if (eq_cnt == 0) first_eq = i;
|
| 2042 |
+
eq_cnt++;
|
| 2043 |
+
}
|
| 2044 |
+
}
|
| 2045 |
+
if (eq_cnt == 1 && first_eq > 0) {
|
| 2046 |
+
char lhs_tmp[32];
|
| 2047 |
+
copy_trim(lhs_tmp, sizeof lhs_tmp, expr_buf, first_eq);
|
| 2048 |
+
if (streq(lhs_tmp, "y") || streq(lhs_tmp, "Y") ||
|
| 2049 |
+
starts_with_ci(lhs_tmp, "f(x)") || starts_with_ci(lhs_tmp, "g(x)")) {
|
| 2050 |
+
copy_trim(expr_buf, sizeof expr_buf, expr_buf + first_eq + 1, slen(expr_buf) - first_eq - 1);
|
| 2051 |
+
}
|
| 2052 |
+
}
|
| 2053 |
+
}
|
| 2054 |
+
|
| 2055 |
+
static int has_alpha_outside_gcd(const char *s) {
|
| 2056 |
+
for (int i = 0; s[i]; ) {
|
| 2057 |
+
if (starts_with_ci(s + i, "gcd(")) { i += 4; continue; }
|
| 2058 |
+
if (is_alpha(s[i])) return 1;
|
| 2059 |
+
i++;
|
| 2060 |
+
}
|
| 2061 |
+
return 0;
|
| 2062 |
+
}
|
| 2063 |
+
|
| 2064 |
+
static void dispatch_call(void) {
|
| 2065 |
+
parse_call_payload();
|
| 2066 |
+
if (!expr_buf[0]) {
|
| 2067 |
+
puts_raw("error: empty expression\n");
|
| 2068 |
+
return;
|
| 2069 |
+
}
|
| 2070 |
+
|
| 2071 |
+
q32_t xmin = -(5LL << 32), xmax = 5LL << 32;
|
| 2072 |
+
int got_bounds = 0;
|
| 2073 |
+
if (has_xmin && parse_q32_num(xmin_buf, NULL, &xmin)) got_bounds = 1;
|
| 2074 |
+
if (has_xmax && parse_q32_num(xmax_buf, NULL, &xmax)) got_bounds = 1;
|
| 2075 |
+
|
| 2076 |
+
// 1. Check for Equation (`LHS = RHS`)
|
| 2077 |
+
int eq_pos = -1;
|
| 2078 |
+
for (int i = 0; expr_buf[i]; i++) {
|
| 2079 |
+
if (expr_buf[i] == '=') { eq_pos = i; break; }
|
| 2080 |
+
}
|
| 2081 |
+
if (eq_pos > 0 && expr_buf[eq_pos + 1]) {
|
| 2082 |
+
char lhs_s[160], rhs_s[160];
|
| 2083 |
+
copy_trim(lhs_s, sizeof lhs_s, expr_buf, eq_pos);
|
| 2084 |
+
copy_trim(rhs_s, sizeof rhs_s, expr_buf + eq_pos + 1, slen(expr_buf) - eq_pos - 1);
|
| 2085 |
+
if (run_solver_mode(expr_buf, lhs_s, rhs_s, got_bounds, xmin, xmax) == 0) return;
|
| 2086 |
+
puts_raw("error: invalid equation syntax\n");
|
| 2087 |
+
return;
|
| 2088 |
+
}
|
| 2089 |
+
|
| 2090 |
+
// 2. Pure Bignum / Exact Rational Arithmetic (no variable x, no trig/log/sqrt)
|
| 2091 |
+
int is_explicit_graph_fn = streq_ci(fn_buf, "graph") || streq_ci(fn_buf, "plot") || streq_ci(fn_buf, "table");
|
| 2092 |
+
if (!has_alpha_outside_gcd(expr_buf) && !got_bounds && !is_explicit_graph_fn) {
|
| 2093 |
+
if (run_bignum_engine(expr_buf) == 0) return;
|
| 2094 |
+
}
|
| 2095 |
+
|
| 2096 |
+
// 3. Compile RPN for Scientific Numeric Eval or Function Graphing / Table
|
| 2097 |
+
RpnProg prog;
|
| 2098 |
+
if (!compile_rpn(expr_buf, &prog)) {
|
| 2099 |
+
puts_raw("error: syntax error in expression\n");
|
| 2100 |
+
return;
|
| 2101 |
+
}
|
| 2102 |
+
|
| 2103 |
+
int force_graph = got_bounds || is_explicit_graph_fn;
|
| 2104 |
+
if (!prog.uses_x && !force_graph) {
|
| 2105 |
+
char step_log[512];
|
| 2106 |
+
int ok = 0;
|
| 2107 |
+
q32_t val = eval_rpn(&prog, 0, &ok, step_log, sizeof step_log);
|
| 2108 |
+
if (!ok) {
|
| 2109 |
+
puts_raw("error: math domain or overflow error\n");
|
| 2110 |
+
return;
|
| 2111 |
+
}
|
| 2112 |
+
puts_raw("expression: ");
|
| 2113 |
+
puts_raw(expr_buf);
|
| 2114 |
+
rv_putchar('\n');
|
| 2115 |
+
if (step_log[0]) puts_raw(step_log);
|
| 2116 |
+
char vbuf[48];
|
| 2117 |
+
fmt_q32_rich(val, vbuf, sizeof vbuf);
|
| 2118 |
+
puts_raw("value: ");
|
| 2119 |
+
puts_raw(vbuf);
|
| 2120 |
+
rv_putchar('\n');
|
| 2121 |
+
return;
|
| 2122 |
+
}
|
| 2123 |
+
|
| 2124 |
+
int table_only = starts_with_ci(fn_buf, "table");
|
| 2125 |
+
run_graph_mode(expr_buf, &prog, xmin, xmax, table_only);
|
| 2126 |
+
}
|
| 2127 |
+
|
| 2128 |
+
#ifndef QEMU
|
| 2129 |
+
// P3 Bridge memory & CarbonText extended special tokens (256..264)
|
| 2130 |
+
#define TEXT_BUF ((const volatile unsigned char *)0x8000)
|
| 2131 |
+
#define CAP_ROWS 80
|
| 2132 |
+
|
| 2133 |
+
enum {
|
| 2134 |
+
TOK_TOOL_RESPONSE = 256,
|
| 2135 |
+
TOK_TOOL_RESPONSE_END = 257,
|
| 2136 |
+
TOK_THINK = 258,
|
| 2137 |
+
TOK_THINK_END = 259,
|
| 2138 |
+
TOK_IM_END = 260,
|
| 2139 |
+
TOK_IM_START = 261,
|
| 2140 |
+
TOK_USER = 262,
|
| 2141 |
+
TOK_ASSISTANT = 263,
|
| 2142 |
+
TOK_NL2 = 264
|
| 2143 |
+
};
|
| 2144 |
+
|
| 2145 |
+
static void put_tok(int c) { rv_ecall2(1, c, 0, 0); }
|
| 2146 |
+
|
| 2147 |
+
static int gather_bridge_text(void) {
|
| 2148 |
+
int n = 0;
|
| 2149 |
+
for (int i = 1; i <= CAP_ROWS; i++) {
|
| 2150 |
+
const volatile unsigned char *r = TEXT_BUF + 16 * i;
|
| 2151 |
+
if (!r[0]) break;
|
| 2152 |
+
for (int k = 0; k < 16 && r[k]; k++) {
|
| 2153 |
+
if (n < (int)sizeof(raw_call) - 1) raw_call[n++] = (char)r[k];
|
| 2154 |
+
}
|
| 2155 |
+
}
|
| 2156 |
+
raw_call[n] = 0;
|
| 2157 |
+
return n;
|
| 2158 |
+
}
|
| 2159 |
+
|
| 2160 |
+
int main(void) {
|
| 2161 |
+
for (;;) {
|
| 2162 |
+
// Park until Qwen emits </tool_call> (GO resumes right after this exit ecall)
|
| 2163 |
+
rv_ecall2(93, 0, 0, 0);
|
| 2164 |
+
int n = gather_bridge_text();
|
| 2165 |
+
|
| 2166 |
+
// Native Qwen3.5 ChatML tool-response turn header:
|
| 2167 |
+
// <|im_end|>\n<|im_start|>user\n<tool_response>\n
|
| 2168 |
+
put_tok(TOK_IM_END);
|
| 2169 |
+
put_tok('\n');
|
| 2170 |
+
put_tok(TOK_IM_START);
|
| 2171 |
+
put_tok(TOK_USER);
|
| 2172 |
+
put_tok('\n');
|
| 2173 |
+
put_tok(TOK_TOOL_RESPONSE);
|
| 2174 |
+
put_tok('\n');
|
| 2175 |
+
|
| 2176 |
+
if (n <= 0) {
|
| 2177 |
+
puts_raw("error: empty tool call\n");
|
| 2178 |
+
} else {
|
| 2179 |
+
dispatch_call();
|
| 2180 |
+
}
|
| 2181 |
+
|
| 2182 |
+
// Native Qwen3.5 ChatML assistant continuation header:
|
| 2183 |
+
// </tool_response><|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n##[HALT -> ' ']
|
| 2184 |
+
put_tok(TOK_TOOL_RESPONSE_END);
|
| 2185 |
+
put_tok(TOK_IM_END);
|
| 2186 |
+
put_tok('\n');
|
| 2187 |
+
put_tok(TOK_IM_START);
|
| 2188 |
+
put_tok(TOK_ASSISTANT);
|
| 2189 |
+
put_tok('\n');
|
| 2190 |
+
put_tok(TOK_THINK);
|
| 2191 |
+
put_tok(TOK_NL2);
|
| 2192 |
+
put_tok(TOK_THINK_END);
|
| 2193 |
+
put_tok(TOK_NL2);
|
| 2194 |
+
put_tok('#');
|
| 2195 |
+
put_tok('#');
|
| 2196 |
+
}
|
| 2197 |
+
}
|
| 2198 |
+
#else
|
| 2199 |
+
int main(void) {
|
| 2200 |
+
int pos = 0;
|
| 2201 |
+
while (pos < (int)sizeof(raw_call) - 1) {
|
| 2202 |
+
long got = rv_ecall2(63, 0, (long)(raw_call + pos), (long)(sizeof(raw_call) - 1 - pos));
|
| 2203 |
+
if (got <= 0) break;
|
| 2204 |
+
pos += (int)got;
|
| 2205 |
+
}
|
| 2206 |
+
raw_call[pos] = 0;
|
| 2207 |
+
dispatch_call();
|
| 2208 |
+
return 0;
|
| 2209 |
+
}
|
| 2210 |
+
#endif
|
firmware/tables.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generate DROM fixed-point math lookup tables (`graphcalc_tables.h`) and bignum print sleds (`sled.S`)."""
|
| 2 |
+
import math
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def write_tables(hdr_path: Path) -> None:
|
| 8 |
+
one32 = 1 << 32
|
| 9 |
+
lines = [
|
| 10 |
+
"// Auto-generated Q32.32 constants and CORDIC / log tables in DROM (.rodata).",
|
| 11 |
+
"#pragma once",
|
| 12 |
+
"#include <stdint.h>",
|
| 13 |
+
"",
|
| 14 |
+
f"#define Q32_ONE ({one32}LL)",
|
| 15 |
+
f"#define Q32_PI ({round(math.pi * one32)}LL)",
|
| 16 |
+
f"#define Q32_TWO_PI ({round(2.0 * math.pi * one32)}LL)",
|
| 17 |
+
f"#define Q32_INV_TWO_PI ({round((1.0 / (2.0 * math.pi)) * one32)}LL)",
|
| 18 |
+
f"#define Q32_HALF_PI ({round(0.5 * math.pi * one32)}LL)",
|
| 19 |
+
f"#define Q32_E ({round(math.e * one32)}LL)",
|
| 20 |
+
f"#define Q32_LN2 ({round(math.log(2.0) * one32)}LL)",
|
| 21 |
+
f"#define Q32_INV_LN2 ({round((1.0 / math.log(2.0)) * one32)}LL)",
|
| 22 |
+
f"#define Q32_LN10 ({round(math.log(10.0) * one32)}LL)",
|
| 23 |
+
"",
|
| 24 |
+
]
|
| 25 |
+
# CORDIC gain K = prod_{i=0}^{29} 1 / sqrt(1 + 2^{-2i})
|
| 26 |
+
k_gain = 1.0
|
| 27 |
+
atan_vals = []
|
| 28 |
+
for i in range(30):
|
| 29 |
+
k_gain /= math.hypot(1.0, 2.0 ** (-i))
|
| 30 |
+
atan_vals.append(round(math.atan(2.0 ** (-i)) * one32))
|
| 31 |
+
lines.append(f"#define CORDIC_K ({round(k_gain * one32)}LL)")
|
| 32 |
+
lines.append("static const int64_t CORDIC_ATAN[30] = {")
|
| 33 |
+
for i in range(0, 30, 5):
|
| 34 |
+
chunk = ", ".join(f"{v}LL" for v in atan_vals[i : i + 5])
|
| 35 |
+
lines.append(f" {chunk},")
|
| 36 |
+
lines.append("};\n")
|
| 37 |
+
|
| 38 |
+
# Shifted log table: ln(1 + 2^{-i}) for i = 1..30 in Q32.32
|
| 39 |
+
ln1p_vals = [0] + [round(math.log1p(2.0 ** (-i)) * one32) for i in range(1, 31)]
|
| 40 |
+
lines.append("static const int64_t LN1P_POW2[31] = {")
|
| 41 |
+
for i in range(0, 31, 5):
|
| 42 |
+
chunk = ", ".join(f"{v}LL" for v in ln1p_vals[i : i + 5])
|
| 43 |
+
lines.append(f" {chunk},")
|
| 44 |
+
lines.append("};\n")
|
| 45 |
+
|
| 46 |
+
hdr_path.write_text("\n".join(lines), encoding="utf-8")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def write_sled(sled_path: Path) -> None:
|
| 50 |
+
out = [" .section .text", " .globl bn_sleds"]
|
| 51 |
+
for r in range(8):
|
| 52 |
+
out += [f"bn_sled{r}:"] + [f" .insn i 0x2B, 0, x{r}, x0, {k}" for k in range(127, -1, -1)]
|
| 53 |
+
out += [" ret"]
|
| 54 |
+
out += [" .section .rodata", " .align 2", "bn_sleds:"]
|
| 55 |
+
out += [f" .word bn_sled{r}" for r in range(8)]
|
| 56 |
+
sled_path.write_text("\n".join(out) + "\n", encoding="utf-8")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
write_tables(Path(sys.argv[1]))
|
| 61 |
+
write_sled(Path(sys.argv[2]))
|
generation_config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"eos_token_id": [
|
| 3 |
+
248046,
|
| 4 |
+
248044
|
| 5 |
+
],
|
| 6 |
+
"pad_token_id": 248044,
|
| 7 |
+
"bad_words_ids": [
|
| 8 |
+
[
|
| 9 |
+
248045
|
| 10 |
+
]
|
| 11 |
+
],
|
| 12 |
+
"do_sample": false
|
| 13 |
+
}
|
graphcalc.elf
ADDED
|
Binary file (87.6 kB). View file
|
|
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c7dacb327bea9361e115a0378d19ce9e030e8aeed3561361a461812ba1cceee7
|
| 3 |
+
size 17971408308
|
runner.py
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Standalone in-weights runner for `Qwen3.5-9B-Math-GraphCalc` (and `0.8B`).
|
| 2 |
+
|
| 3 |
+
Requires `qwenputer` (`https://github.com/divagr18/qwenputer`) on `PYTHONPATH` or pointed to by
|
| 4 |
+
`QWENPUTER_ROOT` (defaults to `./qwenputer` or `../qwenputer`):
|
| 5 |
+
|
| 6 |
+
git clone https://github.com/divagr18/qwenputer
|
| 7 |
+
QWENPUTER_ROOT=./qwenputer python runner.py --ask "Calculate 37429 * 8613 + 194820 and explain your steps."
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import copy
|
| 14 |
+
import gc
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
import re
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
import zlib
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
import numpy as np
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
from safetensors import safe_open
|
| 28 |
+
from tokenizers import Tokenizer
|
| 29 |
+
from transformers import AutoConfig, AutoTokenizer, DynamicCache, Qwen3_5ForCausalLM
|
| 30 |
+
from transformers.models.qwen3_5 import modeling_qwen3_5 as q35_mod
|
| 31 |
+
|
| 32 |
+
MODEL_DIR = Path(__file__).resolve().parent
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _locate_qwenputer_rv() -> Path:
|
| 36 |
+
candidates = []
|
| 37 |
+
if os.environ.get("QWENPUTER_ROOT"):
|
| 38 |
+
candidates.append(Path(os.environ["QWENPUTER_ROOT"]))
|
| 39 |
+
candidates.extend([
|
| 40 |
+
Path.cwd() / "qwenputer",
|
| 41 |
+
Path.cwd().parent / "qwenputer",
|
| 42 |
+
MODEL_DIR / "qwenputer",
|
| 43 |
+
MODEL_DIR.parent / "qwenputer",
|
| 44 |
+
MODEL_DIR.parent.parent / "qwenputer",
|
| 45 |
+
MODEL_DIR.parent.parent.parent / "qwenputer",
|
| 46 |
+
])
|
| 47 |
+
for root in candidates:
|
| 48 |
+
rv = root.resolve() / "experimental" / "riscv"
|
| 49 |
+
if (rv / "chipformer").is_dir():
|
| 50 |
+
return rv
|
| 51 |
+
raise RuntimeError(
|
| 52 |
+
"Could not locate `chipformer` from `qwenputer` (`experimental/riscv/chipformer`). "
|
| 53 |
+
"Clone https://github.com/divagr18/qwenputer and set QWENPUTER_ROOT=/path/to/qwenputer."
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
import chipformer # noqa: F401
|
| 59 |
+
except ImportError:
|
| 60 |
+
sys.path.insert(0, str(_locate_qwenputer_rv()))
|
| 61 |
+
|
| 62 |
+
from chipformer.q35 import fast as F_chip # noqa: E402
|
| 63 |
+
from chipformer.q35 import ir # noqa: E402
|
| 64 |
+
from chipformer.q35.ir import Target # noqa: E402
|
| 65 |
+
from chipformer.rv import bridge as BR # noqa: E402
|
| 66 |
+
from chipformer.rv import core, elf, hf, ref # noqa: E402
|
| 67 |
+
from chipformer.rv import spec as S # noqa: E402
|
| 68 |
+
|
| 69 |
+
CARBON_SPECIALS = [
|
| 70 |
+
"<tool_response>", # 256
|
| 71 |
+
"</tool_response>", # 257
|
| 72 |
+
"<think>", # 258
|
| 73 |
+
"</think>", # 259
|
| 74 |
+
"<|im_end|>", # 260
|
| 75 |
+
"<|im_start|>", # 261
|
| 76 |
+
"user", # 262
|
| 77 |
+
"assistant", # 263
|
| 78 |
+
"\n\n", # 264
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
GRAPHCALC_TOOL = {
|
| 82 |
+
"type": "function",
|
| 83 |
+
"function": {
|
| 84 |
+
"name": "graphcalc",
|
| 85 |
+
"description": (
|
| 86 |
+
"Exact in-weights graphing & math calculator (62-digit integers/rationals, scientific "
|
| 87 |
+
"functions, equation solver, and coordinate plotter). Pass an arithmetic expression, "
|
| 88 |
+
"an equation in x (LHS = RHS), or a function f(x); optionally pass xmin and xmax to "
|
| 89 |
+
"plot y = f(x) and compute roots, vertex/extrema, definite integral, and value table."
|
| 90 |
+
),
|
| 91 |
+
"parameters": {
|
| 92 |
+
"type": "object",
|
| 93 |
+
"properties": {
|
| 94 |
+
"expression": {
|
| 95 |
+
"type": "string",
|
| 96 |
+
"description": "Math expression, equation (LHS = RHS), or function of x (e.g. 'x^2 - 4*x + 3')",
|
| 97 |
+
},
|
| 98 |
+
"xmin": {
|
| 99 |
+
"type": "number",
|
| 100 |
+
"description": "Optional domain/plot minimum x (default -5 for plots, -20 for nonlinear solve)",
|
| 101 |
+
},
|
| 102 |
+
"xmax": {
|
| 103 |
+
"type": "number",
|
| 104 |
+
"description": "Optional domain/plot maximum x (default 5 for plots, 20 for nonlinear solve)",
|
| 105 |
+
},
|
| 106 |
+
},
|
| 107 |
+
"required": ["expression"],
|
| 108 |
+
},
|
| 109 |
+
},
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
GRAPHCALC_SYSTEM_PROMPT = (
|
| 113 |
+
"You are a clear, encouraging step-by-step math tutor equipped with an embedded graphing "
|
| 114 |
+
"calculator (`graphcalc`). Always call `graphcalc` first to compute exact arithmetic, roots, "
|
| 115 |
+
"coordinate tables, or function plots, then explain every step clearly so a student can learn."
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
_orig_chunk_gdn = q35_mod.torch_chunk_gated_delta_rule
|
| 119 |
+
_orig_rec_gdn = q35_mod.torch_recurrent_gated_delta_rule
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _fp32_chunk_gdn(*args, **kwargs):
|
| 123 |
+
with torch.autocast("cuda", enabled=False):
|
| 124 |
+
return _orig_chunk_gdn(*args, **kwargs)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _fp32_rec_gdn(*args, **kwargs):
|
| 128 |
+
with torch.autocast("cuda", enabled=False):
|
| 129 |
+
return _orig_rec_gdn(*args, **kwargs)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
q35_mod.torch_chunk_gated_delta_rule = _fp32_chunk_gdn
|
| 133 |
+
q35_mod.torch_recurrent_gated_delta_rule = _fp32_rec_gdn
|
| 134 |
+
F_chip.exact_matmuls()
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class CarbonText(BR.Text):
|
| 138 |
+
"""Qwen3.5 bridge vocabulary with native ChatML special tokens (256..264) and space HALT echo."""
|
| 139 |
+
|
| 140 |
+
def __init__(self, tokenizer_path: str | Path) -> None:
|
| 141 |
+
p = Path(tokenizer_path)
|
| 142 |
+
tj = p if p.is_file() else (p / "tokenizer.json")
|
| 143 |
+
self.tok = Tokenizer.from_file(str(tj))
|
| 144 |
+
vocab = self.tok.get_vocab(with_added_tokens=True)
|
| 145 |
+
base_vocab = {s: i for s, i in vocab.items() if not s.startswith("<|chip_")}
|
| 146 |
+
self.n = max(base_vocab.values()) + 1
|
| 147 |
+
dec = {c: b for b, c in BR.bytes_to_unicode().items()}
|
| 148 |
+
added = (
|
| 149 |
+
{a.content for a in self.tok.get_added_tokens_decoder().values()}
|
| 150 |
+
if hasattr(self.tok, "get_added_tokens_decoder")
|
| 151 |
+
else set()
|
| 152 |
+
)
|
| 153 |
+
self.bytes = [b""] * self.n
|
| 154 |
+
for s, i in base_vocab.items():
|
| 155 |
+
if s in added or not all(ch in dec for ch in s):
|
| 156 |
+
self.bytes[i] = s.encode("utf-8")
|
| 157 |
+
else:
|
| 158 |
+
self.bytes[i] = bytes(dec[ch] for ch in s)
|
| 159 |
+
self.TCO = base_vocab["<tool_call>"]
|
| 160 |
+
self.TCC = base_vocab["</tool_call>"]
|
| 161 |
+
u = BR.bytes_to_unicode()
|
| 162 |
+
special_ids = []
|
| 163 |
+
for sp in CARBON_SPECIALS:
|
| 164 |
+
if sp in base_vocab:
|
| 165 |
+
special_ids.append(base_vocab[sp])
|
| 166 |
+
else:
|
| 167 |
+
enc = self.tok.encode(sp, add_special_tokens=False).ids
|
| 168 |
+
assert len(enc) == 1, f"Special {sp!r} is not a single Qwen token: {enc}"
|
| 169 |
+
special_ids.append(enc[0])
|
| 170 |
+
self.printable = [base_vocab[u[b]] for b in range(256)] + special_ids
|
| 171 |
+
self.pindex = {q: p for p, q in enumerate(self.printable)}
|
| 172 |
+
self.NL = self.printable[32]
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _compact_layer_inplace(layer: nn.Module, is_chip: bool) -> tuple[int, int]:
|
| 176 |
+
before = after = 0
|
| 177 |
+
if is_chip and hasattr(layer, "linear_attn") and isinstance(layer.linear_attn.out_proj, nn.Linear):
|
| 178 |
+
F_chip.prune_heads(layer.linear_attn)
|
| 179 |
+
for parent in list(layer.modules()):
|
| 180 |
+
for name, child in list(parent.named_children()):
|
| 181 |
+
if isinstance(child, nn.Linear) and child.bias is None:
|
| 182 |
+
sp = F_chip.SparseLinear(child.weight)
|
| 183 |
+
before += child.weight.numel()
|
| 184 |
+
after += sp.weight.numel()
|
| 185 |
+
setattr(parent, name, sp)
|
| 186 |
+
return before, after
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _load_and_compact_qwen35(path: Path, n_chip: int, dev: str) -> tuple[Qwen3_5ForCausalLM, dict]:
|
| 190 |
+
layout_file = path / "chip_layout.json"
|
| 191 |
+
if not layout_file.is_file():
|
| 192 |
+
model = Qwen3_5ForCausalLM.from_pretrained(
|
| 193 |
+
str(path),
|
| 194 |
+
dtype=torch.bfloat16,
|
| 195 |
+
attn_implementation="sdpa",
|
| 196 |
+
)
|
| 197 |
+
chip_b = chip_a = qwen_b = qwen_a = 0
|
| 198 |
+
for i, layer in enumerate(model.model.layers):
|
| 199 |
+
b, a = _compact_layer_inplace(layer, is_chip=(i < n_chip))
|
| 200 |
+
if i < n_chip:
|
| 201 |
+
chip_b += b
|
| 202 |
+
chip_a += a
|
| 203 |
+
else:
|
| 204 |
+
qwen_b += b
|
| 205 |
+
qwen_a += a
|
| 206 |
+
return model.to(dev).eval(), {
|
| 207 |
+
"chip_dense": chip_b,
|
| 208 |
+
"chip_compact": chip_a,
|
| 209 |
+
"qwen_dense": qwen_b,
|
| 210 |
+
"qwen_compact": qwen_a,
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
layout = json.loads(layout_file.read_text(encoding="utf-8"))
|
| 214 |
+
blob = (path / "chip_indices.bin.zlib").read_bytes()
|
| 215 |
+
cfg = AutoConfig.from_pretrained(str(path))
|
| 216 |
+
cfg._attn_implementation = "sdpa"
|
| 217 |
+
with torch.device("meta"):
|
| 218 |
+
model = Qwen3_5ForCausalLM(cfg)
|
| 219 |
+
|
| 220 |
+
tensors_spec: dict[str, dict] = layout["tensors"]
|
| 221 |
+
by_layer: list[list[str]] = [[] for _ in range(cfg.num_hidden_layers)]
|
| 222 |
+
for k in tensors_spec:
|
| 223 |
+
if k.startswith("model.layers."):
|
| 224 |
+
lid = int(k.split(".", 3)[2])
|
| 225 |
+
by_layer[lid].append(k)
|
| 226 |
+
|
| 227 |
+
chip_before = chip_after = qwen_before = qwen_after = head_before = head_after = 0
|
| 228 |
+
with safe_open(str(path / "model.safetensors"), framework="pt", device="cpu") as f:
|
| 229 |
+
def _unpack_one(k: str) -> torch.Tensor:
|
| 230 |
+
spec = tensors_spec[k]
|
| 231 |
+
vals = f.get_tensor(k)
|
| 232 |
+
if spec["mode"] == "dense":
|
| 233 |
+
return vals
|
| 234 |
+
shape = tuple(spec["shape"])
|
| 235 |
+
raw = zlib.decompress(blob[spec["offset"] : spec["offset"] + spec["length"]])
|
| 236 |
+
idx = np.cumsum(np.frombuffer(raw, dtype=np.uint32), dtype=np.int64)
|
| 237 |
+
out = torch.zeros(int(np.prod(shape)), dtype=vals.dtype)
|
| 238 |
+
out[torch.from_numpy(idx)] = vals
|
| 239 |
+
return out.view(shape)
|
| 240 |
+
|
| 241 |
+
for lid in range(cfg.num_hidden_layers):
|
| 242 |
+
prefix = f"model.layers.{lid}."
|
| 243 |
+
sub_sd = {k[len(prefix) :]: _unpack_one(k) for k in by_layer[lid]}
|
| 244 |
+
layer = model.model.layers[lid]
|
| 245 |
+
layer.load_state_dict(sub_sd, assign=True, strict=True)
|
| 246 |
+
del sub_sd
|
| 247 |
+
b, a = _compact_layer_inplace(layer, is_chip=(lid < n_chip))
|
| 248 |
+
if lid < n_chip:
|
| 249 |
+
chip_before += b
|
| 250 |
+
chip_after += a
|
| 251 |
+
else:
|
| 252 |
+
qwen_before += b
|
| 253 |
+
qwen_after += a
|
| 254 |
+
layer.to(dev)
|
| 255 |
+
|
| 256 |
+
norm_w = _unpack_one("model.norm.weight").to(dev)
|
| 257 |
+
model.model.norm.load_state_dict({"weight": norm_w}, assign=True, strict=True)
|
| 258 |
+
|
| 259 |
+
emb_w = _unpack_one("model.embed_tokens.weight").to(dev)
|
| 260 |
+
model.model.embed_tokens.load_state_dict({"weight": emb_w}, assign=True, strict=True)
|
| 261 |
+
|
| 262 |
+
if "lm_head.weight" in tensors_spec and not getattr(cfg, "tie_word_embeddings", True):
|
| 263 |
+
head_w_cpu = _unpack_one("lm_head.weight")
|
| 264 |
+
head_before = head_w_cpu.numel()
|
| 265 |
+
hc0, hc1 = F_chip._hull(F_chip.nonzero_cols(head_w_cpu))
|
| 266 |
+
hblock = nn.Parameter(head_w_cpu[:, hc0:hc1].contiguous().to(dev), requires_grad=False)
|
| 267 |
+
head_after = hblock.numel()
|
| 268 |
+
model.lm_head = F_chip.SparseLinear(
|
| 269 |
+
head_w_cpu, rows=(0, head_w_cpu.shape[0]), cols=(hc0, hc1), block=hblock
|
| 270 |
+
)
|
| 271 |
+
del head_w_cpu
|
| 272 |
+
else:
|
| 273 |
+
model.lm_head.load_state_dict({"weight": emb_w}, assign=True, strict=True)
|
| 274 |
+
model.tie_weights()
|
| 275 |
+
head_before = head_after = emb_w.numel()
|
| 276 |
+
|
| 277 |
+
model.model.rotary_emb = q35_mod.Qwen3_5TextRotaryEmbedding(config=cfg).to(dev)
|
| 278 |
+
model.eval()
|
| 279 |
+
if dev == "cuda":
|
| 280 |
+
with torch.no_grad():
|
| 281 |
+
for _ in range(2):
|
| 282 |
+
_wc = DynamicCache(config=cfg)
|
| 283 |
+
model.model(
|
| 284 |
+
input_ids=torch.zeros((1, 2), dtype=torch.long, device=dev),
|
| 285 |
+
past_key_values=_wc,
|
| 286 |
+
use_cache=True,
|
| 287 |
+
)
|
| 288 |
+
model.model(
|
| 289 |
+
input_ids=torch.zeros((1, 1), dtype=torch.long, device=dev),
|
| 290 |
+
past_key_values=_wc,
|
| 291 |
+
use_cache=True,
|
| 292 |
+
)
|
| 293 |
+
del _wc
|
| 294 |
+
gc.collect()
|
| 295 |
+
torch.cuda.empty_cache()
|
| 296 |
+
else:
|
| 297 |
+
gc.collect()
|
| 298 |
+
return model, {
|
| 299 |
+
"chip_dense": chip_before,
|
| 300 |
+
"chip_compact": chip_after,
|
| 301 |
+
"qwen_dense": qwen_before,
|
| 302 |
+
"qwen_compact": qwen_after,
|
| 303 |
+
"head_dense": head_before,
|
| 304 |
+
"head_compact": head_after,
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
class CarbonGraphCalcRunner:
|
| 309 |
+
"""Self-contained runner for `Qwen3.5-Math-GraphCalc` checkpoints."""
|
| 310 |
+
|
| 311 |
+
def __init__(
|
| 312 |
+
self,
|
| 313 |
+
model_dir: str | Path = MODEL_DIR,
|
| 314 |
+
firmware_path: str | Path | None = None,
|
| 315 |
+
device: str | None = None,
|
| 316 |
+
fast: bool = True,
|
| 317 |
+
executor: bool | str = True,
|
| 318 |
+
) -> None:
|
| 319 |
+
torch.set_grad_enabled(False)
|
| 320 |
+
F_chip.exact_matmuls()
|
| 321 |
+
self.path = Path(model_dir)
|
| 322 |
+
self.meta = json.loads((self.path / "chipformer.json").read_text(encoding="utf-8"))
|
| 323 |
+
self.dev = device or ("cuda" if torch.cuda.is_available() else "cpu")
|
| 324 |
+
self.n_chip = int(self.meta["chip_layers"])
|
| 325 |
+
self.v0 = int(self.meta["chip_token_offset"])
|
| 326 |
+
self.d0 = int(self.meta["chip_dims_offset"])
|
| 327 |
+
|
| 328 |
+
self.model, self.compact_stats = _load_and_compact_qwen35(self.path, self.n_chip, self.dev)
|
| 329 |
+
self.lm = self.model.model
|
| 330 |
+
if isinstance(self.model.lm_head, F_chip.SparseLinear):
|
| 331 |
+
self.head_c0 = self.model.lm_head.c0
|
| 332 |
+
self.head_c1 = self.model.lm_head.c1
|
| 333 |
+
self.qwen_head_w = self.model.lm_head.weight[: self.v0]
|
| 334 |
+
else:
|
| 335 |
+
self.head_c0 = 0
|
| 336 |
+
self.head_c1 = self.model.config.hidden_size
|
| 337 |
+
self.qwen_head_w = self.model.lm_head.weight[: self.v0]
|
| 338 |
+
self._p4_buf = torch.zeros((4, 1, 1), dtype=torch.long, device=self.dev)
|
| 339 |
+
|
| 340 |
+
self.tok = AutoTokenizer.from_pretrained(str(self.path))
|
| 341 |
+
self.T = CarbonText(self.path / "tokenizer.json")
|
| 342 |
+
|
| 343 |
+
cfg = self.model.config
|
| 344 |
+
self.c = core.build(
|
| 345 |
+
Target(cfg.linear_num_key_heads, cfg.linear_num_value_heads, cfg.intermediate_size),
|
| 346 |
+
self.T,
|
| 347 |
+
)
|
| 348 |
+
self.code_dims = torch.tensor(self.meta["code_dims"], device=self.dev, dtype=torch.long)
|
| 349 |
+
self.eos = {248046, 248044}
|
| 350 |
+
self.go_model_id = self.v0 + S.tok("GO")
|
| 351 |
+
|
| 352 |
+
fw = firmware_path or (self.path / self.meta.get("firmware", "graphcalc.elf"))
|
| 353 |
+
self.firmware_path = Path(fw)
|
| 354 |
+
self.prog = elf.program(self.firmware_path)
|
| 355 |
+
core.snapshot(self.c, self.fresh_machine())
|
| 356 |
+
|
| 357 |
+
self.executor = executor if self.dev == "cuda" else False
|
| 358 |
+
self.verify = self.executor == "verify"
|
| 359 |
+
if self.executor:
|
| 360 |
+
try:
|
| 361 |
+
self._build_exec()
|
| 362 |
+
except Exception as exc:
|
| 363 |
+
if self.verify:
|
| 364 |
+
raise
|
| 365 |
+
print(f"[runner] Triton Executor2 unavailable ({type(exc).__name__}: {exc}); using CUDA-graphed HF chip layers.", flush=True)
|
| 366 |
+
self.executor = False
|
| 367 |
+
self.verify = False
|
| 368 |
+
|
| 369 |
+
self.fast = bool(fast and self.dev == "cuda" and (not self.executor or self.verify))
|
| 370 |
+
if self.fast:
|
| 371 |
+
self._build_fast()
|
| 372 |
+
|
| 373 |
+
def _build_exec(self) -> None:
|
| 374 |
+
from chipformer.rv.chat import ExecRow
|
| 375 |
+
|
| 376 |
+
self.keep_ir = torch.tensor(self.c.keep, device=self.dev, dtype=torch.long)
|
| 377 |
+
cols = self.meta["code_dims"] + [self.meta["tau_dim"], self.meta["mu_dim"]]
|
| 378 |
+
self.keep_cols = torch.tensor(cols, device=self.dev, dtype=torch.long)
|
| 379 |
+
self.emb_cache: dict[int, torch.Tensor] = {}
|
| 380 |
+
self.erow = ExecRow(self)
|
| 381 |
+
self.erow._capture()
|
| 382 |
+
|
| 383 |
+
def _exec_emb(self, ids: list[int]) -> torch.Tensor:
|
| 384 |
+
new = sorted({i for i in ids if i not in self.emb_cache})
|
| 385 |
+
if new:
|
| 386 |
+
rows = self.erow.ex.embed(
|
| 387 |
+
[i - self.v0 if i >= self.v0 else BR.text_token(i) for i in new]
|
| 388 |
+
)
|
| 389 |
+
for i, r in zip(new, rows):
|
| 390 |
+
self.emb_cache[i] = r
|
| 391 |
+
return torch.stack([self.emb_cache[i] for i in ids])
|
| 392 |
+
|
| 393 |
+
@torch.no_grad()
|
| 394 |
+
def _exec_step_one(self, tok_id: int) -> torch.Tensor:
|
| 395 |
+
row = self.emb_cache.get(tok_id)
|
| 396 |
+
if row is None:
|
| 397 |
+
st = tok_id - self.v0 if tok_id >= self.v0 else BR.text_token(tok_id)
|
| 398 |
+
row = self.erow.ex.embed([st])[0]
|
| 399 |
+
self.emb_cache[tok_id] = row
|
| 400 |
+
self.erow.xbuf[0].copy_(row)
|
| 401 |
+
self.erow.graph.replay()
|
| 402 |
+
return self.erow.kbuf
|
| 403 |
+
|
| 404 |
+
@torch.no_grad()
|
| 405 |
+
def _exec_start_collect(self, ids: list[int]) -> torch.Tensor:
|
| 406 |
+
if self.erow.graph is None:
|
| 407 |
+
self.erow._capture()
|
| 408 |
+
for st in self.erow.heads:
|
| 409 |
+
st["S"].zero_()
|
| 410 |
+
st["hist"].zero_()
|
| 411 |
+
emb = self._exec_emb(ids)
|
| 412 |
+
keeps = torch.empty((len(ids), len(self.keep_cols)), dtype=torch.float32, device=self.dev)
|
| 413 |
+
for t in range(len(ids)):
|
| 414 |
+
self.erow.xbuf.copy_(emb[t : t + 1])
|
| 415 |
+
self.erow.graph.replay()
|
| 416 |
+
keeps[t].copy_(self.erow.kbuf)
|
| 417 |
+
self.erow.ex.load_init()
|
| 418 |
+
return keeps
|
| 419 |
+
|
| 420 |
+
def _exec_hc(self, tok_id: int, keep: torch.Tensor) -> torch.Tensor:
|
| 421 |
+
hc = torch.zeros(self.lm.embed_tokens.weight.shape[1], dtype=torch.bfloat16, device=self.dev)
|
| 422 |
+
hc[: self.d0] = self.lm.embed_tokens.weight[tok_id, : self.d0]
|
| 423 |
+
hc[self.keep_cols] = keep.to(torch.bfloat16)
|
| 424 |
+
return hc
|
| 425 |
+
|
| 426 |
+
def _build_fast(self) -> None:
|
| 427 |
+
cfg = copy.deepcopy(self.model.config)
|
| 428 |
+
cfg.num_hidden_layers = self.n_chip + 1
|
| 429 |
+
cfg.layer_types = ["linear_attention"] * self.n_chip + ["full_attention"]
|
| 430 |
+
self.ccfg = cfg
|
| 431 |
+
dummy = q35_mod.Qwen3_5DecoderLayer(cfg, self.n_chip).to(self.dev, torch.bfloat16)
|
| 432 |
+
for p in dummy.parameters():
|
| 433 |
+
p.data.zero_()
|
| 434 |
+
with torch.device("meta"):
|
| 435 |
+
cm = q35_mod.Qwen3_5TextModel(cfg)
|
| 436 |
+
cm.embed_tokens = self.lm.embed_tokens
|
| 437 |
+
cm.layers = nn.ModuleList([*self.lm.layers[: self.n_chip], dummy])
|
| 438 |
+
cm.norm = nn.Identity()
|
| 439 |
+
cm.rotary_emb = q35_mod.Qwen3_5TextRotaryEmbedding(config=cfg).to(self.dev)
|
| 440 |
+
self.cm = cm.eval()
|
| 441 |
+
self.graph = None
|
| 442 |
+
|
| 443 |
+
def _chip_cache(self):
|
| 444 |
+
if getattr(self, "ccache", None) is None:
|
| 445 |
+
self.ccache = DynamicCache(config=self.ccfg)
|
| 446 |
+
else:
|
| 447 |
+
for L in range(self.n_chip):
|
| 448 |
+
lay = self.ccache.layers[L]
|
| 449 |
+
lay.conv_states[0].zero_()
|
| 450 |
+
lay.recurrent_states[0].zero_()
|
| 451 |
+
lay.has_previous_state[0] = False
|
| 452 |
+
return self.ccache
|
| 453 |
+
|
| 454 |
+
@torch.no_grad()
|
| 455 |
+
def _chip_body(self):
|
| 456 |
+
h = self.cm.embed_tokens(self.g_ids)
|
| 457 |
+
for L in range(self.n_chip):
|
| 458 |
+
h = self.cm.layers[L](
|
| 459 |
+
h,
|
| 460 |
+
position_embeddings=None,
|
| 461 |
+
attention_mask=None,
|
| 462 |
+
position_ids=None,
|
| 463 |
+
past_key_values=self.ccache,
|
| 464 |
+
)
|
| 465 |
+
self.g_out.copy_(h)
|
| 466 |
+
|
| 467 |
+
@torch.no_grad()
|
| 468 |
+
def _capture(self):
|
| 469 |
+
self.g_ids = torch.zeros((1, 1), dtype=torch.long, device=self.dev)
|
| 470 |
+
self.g_out = torch.zeros(
|
| 471 |
+
(1, 1, self.model.config.hidden_size), dtype=torch.bfloat16, device=self.dev
|
| 472 |
+
)
|
| 473 |
+
lays = [self.ccache.layers[L] for L in range(self.n_chip)]
|
| 474 |
+
saved = [(lay.conv_states[0].clone(), lay.recurrent_states[0].clone()) for lay in lays]
|
| 475 |
+
s = torch.cuda.Stream()
|
| 476 |
+
s.wait_stream(torch.cuda.current_stream())
|
| 477 |
+
with torch.cuda.stream(s):
|
| 478 |
+
for _ in range(3):
|
| 479 |
+
self._chip_body()
|
| 480 |
+
torch.cuda.current_stream().wait_stream(s)
|
| 481 |
+
self.graph = torch.cuda.CUDAGraph()
|
| 482 |
+
with torch.cuda.graph(self.graph):
|
| 483 |
+
self._chip_body()
|
| 484 |
+
for lay, (cs, rs) in zip(lays, saved):
|
| 485 |
+
lay.conv_states[0].copy_(cs)
|
| 486 |
+
lay.recurrent_states[0].copy_(rs)
|
| 487 |
+
|
| 488 |
+
@torch.no_grad()
|
| 489 |
+
def _chip_step(self, tok_id: int, g: int, cache, compiled: bool = True) -> torch.Tensor:
|
| 490 |
+
if not compiled or self.graph is None:
|
| 491 |
+
x = torch.tensor([[tok_id]], device=self.dev)
|
| 492 |
+
out = self.cm(
|
| 493 |
+
input_ids=x,
|
| 494 |
+
past_key_values=cache,
|
| 495 |
+
use_cache=True,
|
| 496 |
+
cache_position=torch.tensor([g], device=self.dev),
|
| 497 |
+
)
|
| 498 |
+
return out.last_hidden_state[0, -1].clone()
|
| 499 |
+
self.g_ids.fill_(tok_id)
|
| 500 |
+
self.graph.replay()
|
| 501 |
+
return self.g_out[0, -1].clone()
|
| 502 |
+
|
| 503 |
+
@torch.no_grad()
|
| 504 |
+
def _qwen_step(self, hc: torch.Tensor, cache, pos: int, full_head: bool = False) -> torch.Tensor:
|
| 505 |
+
h = hc.view(1, 1, -1)
|
| 506 |
+
p4 = self._p4_buf
|
| 507 |
+
p4.fill_(pos)
|
| 508 |
+
pe = self.lm.rotary_emb(h, p4[1:])
|
| 509 |
+
for i in range(self.n_chip, len(self.lm.layers)):
|
| 510 |
+
h = self.lm.layers[i](
|
| 511 |
+
h,
|
| 512 |
+
position_embeddings=pe,
|
| 513 |
+
attention_mask=None,
|
| 514 |
+
position_ids=p4[0],
|
| 515 |
+
past_key_values=cache,
|
| 516 |
+
)
|
| 517 |
+
nh = self.lm.norm(h)[0, -1]
|
| 518 |
+
if full_head:
|
| 519 |
+
return self.model.lm_head(nh)
|
| 520 |
+
return F.linear(nh[self.head_c0 : self.head_c1], self.qwen_head_w)
|
| 521 |
+
|
| 522 |
+
@torch.no_grad()
|
| 523 |
+
def _qwen_prefill(
|
| 524 |
+
self, x_ids: torch.Tensor, keeps: torch.Tensor, full_head: bool = False
|
| 525 |
+
) -> tuple[DynamicCache, torch.Tensor]:
|
| 526 |
+
p_len = int(x_ids.shape[1])
|
| 527 |
+
h = torch.zeros(
|
| 528 |
+
(1, p_len, self.model.config.hidden_size), dtype=torch.bfloat16, device=self.dev
|
| 529 |
+
)
|
| 530 |
+
h[0, :, : self.d0] = self.lm.embed_tokens.weight[x_ids[0], : self.d0]
|
| 531 |
+
h[0, :, self.keep_cols] = keeps.to(torch.bfloat16)
|
| 532 |
+
cache = DynamicCache(config=self.model.config)
|
| 533 |
+
pos_vec = torch.arange(0, p_len, device=self.dev, dtype=torch.long).unsqueeze(0)
|
| 534 |
+
p4 = pos_vec.unsqueeze(0).expand(4, -1, -1)
|
| 535 |
+
pe = self.lm.rotary_emb(h, p4[1:])
|
| 536 |
+
for i in range(self.n_chip, len(self.lm.layers)):
|
| 537 |
+
h = self.lm.layers[i](
|
| 538 |
+
h,
|
| 539 |
+
position_embeddings=pe,
|
| 540 |
+
attention_mask=None,
|
| 541 |
+
position_ids=p4[0],
|
| 542 |
+
past_key_values=cache,
|
| 543 |
+
)
|
| 544 |
+
nh = self.lm.norm(h)[0, -1]
|
| 545 |
+
logits = (
|
| 546 |
+
self.model.lm_head(nh)
|
| 547 |
+
if full_head
|
| 548 |
+
else F.linear(nh[self.head_c0 : self.head_c1], self.qwen_head_w)
|
| 549 |
+
)
|
| 550 |
+
return cache, logits
|
| 551 |
+
|
| 552 |
+
@torch.no_grad()
|
| 553 |
+
def _qwen_chunk(
|
| 554 |
+
self, hcs: list[torch.Tensor], cache, pos0: int, full_head: bool = False
|
| 555 |
+
) -> torch.Tensor:
|
| 556 |
+
if len(hcs) == 1:
|
| 557 |
+
return self._qwen_step(hcs[0], cache, pos0, full_head=full_head)
|
| 558 |
+
k_len = len(hcs)
|
| 559 |
+
h = torch.stack(hcs, dim=0).unsqueeze(0)
|
| 560 |
+
pos_vec = torch.arange(pos0, pos0 + k_len, device=self.dev, dtype=torch.long).unsqueeze(0)
|
| 561 |
+
p4 = pos_vec.unsqueeze(0).expand(4, -1, -1)
|
| 562 |
+
pe = self.lm.rotary_emb(h, p4[1:])
|
| 563 |
+
attn_mask = torch.zeros((1, 1, k_len, pos0 + k_len), dtype=h.dtype, device=self.dev)
|
| 564 |
+
future = torch.triu(torch.ones((k_len, k_len), dtype=torch.bool, device=self.dev), diagonal=1)
|
| 565 |
+
attn_mask[0, 0, :, pos0:][future] = torch.finfo(h.dtype).min
|
| 566 |
+
for i in range(self.n_chip, len(self.lm.layers)):
|
| 567 |
+
lmask = attn_mask if self.model.config.layer_types[i] == "full_attention" else None
|
| 568 |
+
h = self.lm.layers[i](
|
| 569 |
+
h,
|
| 570 |
+
position_embeddings=pe,
|
| 571 |
+
attention_mask=lmask,
|
| 572 |
+
position_ids=p4[0],
|
| 573 |
+
past_key_values=cache,
|
| 574 |
+
)
|
| 575 |
+
nh = self.lm.norm(h)[0, -1]
|
| 576 |
+
if full_head:
|
| 577 |
+
return self.model.lm_head(nh)
|
| 578 |
+
return F.linear(nh[self.head_c0 : self.head_c1], self.qwen_head_w)
|
| 579 |
+
|
| 580 |
+
@torch.no_grad()
|
| 581 |
+
def _layers_slow(self, tok_id: int, cache, pos: int | None):
|
| 582 |
+
h = self.lm.embed_tokens(torch.tensor([[tok_id]], device=self.dev))
|
| 583 |
+
pe = tp = None
|
| 584 |
+
if pos is not None:
|
| 585 |
+
p4 = torch.full((4, 1, 1), pos, device=self.dev, dtype=torch.long)
|
| 586 |
+
tp = p4[0]
|
| 587 |
+
pe = self.lm.rotary_emb(h, p4[1:])
|
| 588 |
+
hc = None
|
| 589 |
+
n = self.n_chip if pos is None else len(self.lm.layers)
|
| 590 |
+
for i in range(n):
|
| 591 |
+
h = self.lm.layers[i](
|
| 592 |
+
h,
|
| 593 |
+
position_embeddings=pe,
|
| 594 |
+
attention_mask=None,
|
| 595 |
+
position_ids=tp,
|
| 596 |
+
past_key_values=cache,
|
| 597 |
+
)
|
| 598 |
+
if i == self.n_chip - 1:
|
| 599 |
+
hc = h
|
| 600 |
+
logits = None if pos is None else self.model.lm_head(self.lm.norm(h))[0, -1]
|
| 601 |
+
return hc[0, -1], logits
|
| 602 |
+
|
| 603 |
+
def _decode_code_list(self, vals: list[float]) -> int | None:
|
| 604 |
+
if not any(v != 0.0 for v in vals):
|
| 605 |
+
return None
|
| 606 |
+
nf = core.NFAM
|
| 607 |
+
fid = 0
|
| 608 |
+
for i in range(nf):
|
| 609 |
+
if vals[i] > 0.0:
|
| 610 |
+
fid |= 1 << i
|
| 611 |
+
p = 0
|
| 612 |
+
for i in range(len(vals) - nf):
|
| 613 |
+
if vals[nf + i] > 0.0:
|
| 614 |
+
p |= 1 << i
|
| 615 |
+
t = core.decode_code(fid, p, self.T)
|
| 616 |
+
if t < 0:
|
| 617 |
+
raise RuntimeError(f"Invalid circuit token prediction (family={fid}, payload={p})")
|
| 618 |
+
return t - S.TEXT_TOKEN if S.is_text(t) else self.v0 + t
|
| 619 |
+
|
| 620 |
+
def code_token(self, code: torch.Tensor) -> int | None:
|
| 621 |
+
return self._decode_code_list(code.tolist())
|
| 622 |
+
|
| 623 |
+
def circuit_token(self, hc: torch.Tensor) -> int | None:
|
| 624 |
+
return self.code_token(hc[self.code_dims])
|
| 625 |
+
|
| 626 |
+
def fresh_machine(self) -> ref.Machine:
|
| 627 |
+
m = ref.Machine.from_program(self.prog)
|
| 628 |
+
BR.park(m)
|
| 629 |
+
return m
|
| 630 |
+
|
| 631 |
+
def prompt_ids(
|
| 632 |
+
self,
|
| 633 |
+
user_prompt: str | list[dict],
|
| 634 |
+
system_prompt: str | None = GRAPHCALC_SYSTEM_PROMPT,
|
| 635 |
+
use_tools: bool = True,
|
| 636 |
+
) -> list[int]:
|
| 637 |
+
if isinstance(user_prompt, str):
|
| 638 |
+
msgs = []
|
| 639 |
+
if system_prompt:
|
| 640 |
+
msgs.append({"role": "system", "content": system_prompt})
|
| 641 |
+
msgs.append({"role": "user", "content": user_prompt})
|
| 642 |
+
else:
|
| 643 |
+
msgs = list(user_prompt)
|
| 644 |
+
tools = [GRAPHCALC_TOOL] if use_tools else None
|
| 645 |
+
out = self.tok.apply_chat_template(
|
| 646 |
+
msgs,
|
| 647 |
+
tools=tools,
|
| 648 |
+
add_generation_prompt=True,
|
| 649 |
+
enable_thinking=False,
|
| 650 |
+
tokenize=True,
|
| 651 |
+
)
|
| 652 |
+
if not isinstance(out, list):
|
| 653 |
+
out = out["input_ids"]
|
| 654 |
+
return [int(x) for x in out]
|
| 655 |
+
|
| 656 |
+
@torch.no_grad()
|
| 657 |
+
def generate(
|
| 658 |
+
self,
|
| 659 |
+
ids: list[int],
|
| 660 |
+
max_new_text: int = 512,
|
| 661 |
+
max_chip_tokens: int = 500_000,
|
| 662 |
+
max_calls: int = 1,
|
| 663 |
+
check_head: bool = False,
|
| 664 |
+
check_lock: bool = False,
|
| 665 |
+
) -> dict:
|
| 666 |
+
m = self.fresh_machine()
|
| 667 |
+
lock = None
|
| 668 |
+
if check_lock:
|
| 669 |
+
lock = BR.Session(self.T, self.fresh_machine())
|
| 670 |
+
lock.history(ids)
|
| 671 |
+
|
| 672 |
+
x = torch.tensor([ids], device=self.dev, dtype=torch.long)
|
| 673 |
+
core.snapshot(self.c, m)
|
| 674 |
+
n_code = len(self.code_dims)
|
| 675 |
+
|
| 676 |
+
if self.executor and not self.verify:
|
| 677 |
+
keeps = self._exec_start_collect(ids)
|
| 678 |
+
cache, logits = self._qwen_prefill(x, keeps, full_head=check_head)
|
| 679 |
+
else:
|
| 680 |
+
lm_out = self.lm(input_ids=x, use_cache=True)
|
| 681 |
+
cache = lm_out.past_key_values
|
| 682 |
+
if self.executor:
|
| 683 |
+
self.erow.start(ids)
|
| 684 |
+
if self.fast:
|
| 685 |
+
ccache = self._chip_cache()
|
| 686 |
+
h = self.cm.embed_tokens(x)
|
| 687 |
+
for L in range(self.n_chip):
|
| 688 |
+
h = self.cm.layers[L](
|
| 689 |
+
h,
|
| 690 |
+
position_embeddings=None,
|
| 691 |
+
attention_mask=None,
|
| 692 |
+
position_ids=None,
|
| 693 |
+
past_key_values=ccache,
|
| 694 |
+
)
|
| 695 |
+
hf.inject(self.c, ccache)
|
| 696 |
+
if self.graph is None:
|
| 697 |
+
self._capture()
|
| 698 |
+
g = len(ids)
|
| 699 |
+
elif not self.executor:
|
| 700 |
+
hf.inject(self.c, cache)
|
| 701 |
+
logits = self.model.lm_head(lm_out.last_hidden_state[0, -1])
|
| 702 |
+
pos = len(ids)
|
| 703 |
+
gen: list[int] = []
|
| 704 |
+
n_qwen_text = n_echo_text = n_chip_tok = n_calls = head_mismatch = n_verify_bad = 0
|
| 705 |
+
nxt = int(torch.argmax(logits[: self.v0]))
|
| 706 |
+
prev_owned = False
|
| 707 |
+
in_tool_call = False
|
| 708 |
+
pending_hcs: list[torch.Tensor] = []
|
| 709 |
+
pending_pos0: int | None = None
|
| 710 |
+
t0 = time.time()
|
| 711 |
+
expect: list[int] = []
|
| 712 |
+
lock_error = None
|
| 713 |
+
|
| 714 |
+
while n_chip_tok < max_chip_tokens:
|
| 715 |
+
if n_qwen_text >= max_new_text and (not prev_owned) and (not in_tool_call):
|
| 716 |
+
break
|
| 717 |
+
|
| 718 |
+
gen.append(nxt)
|
| 719 |
+
if nxt == self.T.TCO and not prev_owned:
|
| 720 |
+
in_tool_call = True
|
| 721 |
+
elif nxt == self.T.TCC:
|
| 722 |
+
in_tool_call = False
|
| 723 |
+
if nxt == self.go_model_id:
|
| 724 |
+
n_calls += 1
|
| 725 |
+
|
| 726 |
+
if lock is not None:
|
| 727 |
+
st = nxt - self.v0 if nxt >= self.v0 else BR.text_token(nxt)
|
| 728 |
+
if expect:
|
| 729 |
+
e = expect.pop(0)
|
| 730 |
+
if st != e:
|
| 731 |
+
lock_error = {"at": len(gen) - 1, "got": st, "want": e}
|
| 732 |
+
break
|
| 733 |
+
elif st < S.VOCAB:
|
| 734 |
+
lock_error = {"at": len(gen) - 1, "got": S.name(st), "want": "text"}
|
| 735 |
+
break
|
| 736 |
+
else:
|
| 737 |
+
k = len(lock.toks)
|
| 738 |
+
try:
|
| 739 |
+
lock.qwen([nxt])
|
| 740 |
+
except RuntimeError as exc:
|
| 741 |
+
lock_error = {"at": len(gen) - 1, "got": str(exc), "want": "halt"}
|
| 742 |
+
break
|
| 743 |
+
expect = list(lock.toks[k + 1 :])
|
| 744 |
+
|
| 745 |
+
if nxt in self.eos and not prev_owned:
|
| 746 |
+
break
|
| 747 |
+
|
| 748 |
+
chip_in = nxt >= self.v0
|
| 749 |
+
hc = keep = None
|
| 750 |
+
if self.executor:
|
| 751 |
+
keep = self._exec_step_one(nxt)
|
| 752 |
+
if not chip_in:
|
| 753 |
+
hc = self._exec_hc(nxt, keep)
|
| 754 |
+
if self.fast and (self.verify or not self.executor):
|
| 755 |
+
hf_hc = self._chip_step(nxt, g, ccache)
|
| 756 |
+
g += 1
|
| 757 |
+
if self.verify:
|
| 758 |
+
same = (
|
| 759 |
+
bool((hf_hc[self.keep_cols] == keep.to(torch.bfloat16)).all())
|
| 760 |
+
if chip_in
|
| 761 |
+
else bool((hf_hc == hc).all())
|
| 762 |
+
)
|
| 763 |
+
if not same:
|
| 764 |
+
n_verify_bad += 1
|
| 765 |
+
hc = hf_hc
|
| 766 |
+
elif not self.executor:
|
| 767 |
+
hc, logits = self._layers_slow(nxt, cache, None if chip_in else pos)
|
| 768 |
+
|
| 769 |
+
if chip_in:
|
| 770 |
+
n_chip_tok += 1
|
| 771 |
+
nxt = (
|
| 772 |
+
self._decode_code_list(keep[:n_code].tolist())
|
| 773 |
+
if hc is None
|
| 774 |
+
else self.circuit_token(hc)
|
| 775 |
+
)
|
| 776 |
+
prev_owned = True
|
| 777 |
+
if nxt is None:
|
| 778 |
+
raise RuntimeError("Circuit relinquished ownership mid-chip-token")
|
| 779 |
+
else:
|
| 780 |
+
if prev_owned:
|
| 781 |
+
n_echo_text += 1
|
| 782 |
+
else:
|
| 783 |
+
n_qwen_text += 1
|
| 784 |
+
ct = (
|
| 785 |
+
self._decode_code_list(keep[:n_code].tolist())
|
| 786 |
+
if (keep is not None and not self.verify)
|
| 787 |
+
else self.circuit_token(hc)
|
| 788 |
+
)
|
| 789 |
+
if ct is not None and (self.fast or self.executor) and not check_head:
|
| 790 |
+
if pending_pos0 is None:
|
| 791 |
+
pending_pos0 = pos
|
| 792 |
+
pending_hcs.append(hc)
|
| 793 |
+
pos += 1
|
| 794 |
+
nxt = ct
|
| 795 |
+
prev_owned = True
|
| 796 |
+
else:
|
| 797 |
+
if self.fast or self.executor:
|
| 798 |
+
if pending_hcs:
|
| 799 |
+
pending_hcs.append(hc)
|
| 800 |
+
logits = self._qwen_chunk(
|
| 801 |
+
pending_hcs, cache, pending_pos0, full_head=check_head
|
| 802 |
+
)
|
| 803 |
+
pending_hcs.clear()
|
| 804 |
+
pending_pos0 = None
|
| 805 |
+
else:
|
| 806 |
+
logits = self._qwen_step(hc, cache, pos, full_head=check_head)
|
| 807 |
+
pos += 1
|
| 808 |
+
if ct is not None:
|
| 809 |
+
if check_head and int(torch.argmax(logits)) != ct:
|
| 810 |
+
head_mismatch += 1
|
| 811 |
+
nxt = ct
|
| 812 |
+
prev_owned = True
|
| 813 |
+
else:
|
| 814 |
+
q_logits = logits[: self.v0]
|
| 815 |
+
if n_calls >= max_calls or n_qwen_text >= max(16, max_new_text - 48):
|
| 816 |
+
q_logits = q_logits.clone()
|
| 817 |
+
q_logits[self.T.TCO] = float("-inf")
|
| 818 |
+
nxt = int(torch.argmax(q_logits))
|
| 819 |
+
prev_owned = False
|
| 820 |
+
|
| 821 |
+
dt = time.time() - t0
|
| 822 |
+
parsed = self.parse_turn(gen)
|
| 823 |
+
return {
|
| 824 |
+
"tokens": gen,
|
| 825 |
+
"text_tokens": n_qwen_text,
|
| 826 |
+
"echo_text_tokens": n_echo_text,
|
| 827 |
+
"chip_tokens": n_chip_tok,
|
| 828 |
+
"tool_calls_executed": n_calls,
|
| 829 |
+
"seconds": round(dt, 3),
|
| 830 |
+
"head_mismatch": head_mismatch,
|
| 831 |
+
"lock_error": lock_error,
|
| 832 |
+
"verify_mismatch": n_verify_bad if self.verify else None,
|
| 833 |
+
**parsed,
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
def text_of(self, ids: list[int]) -> str:
|
| 837 |
+
return self.tok.decode([i for i in ids if i < self.v0], skip_special_tokens=False)
|
| 838 |
+
|
| 839 |
+
def parse_turn(self, gen_ids: list[int]) -> dict:
|
| 840 |
+
raw = self.text_of(gen_ids)
|
| 841 |
+
calls = re.findall(r"<tool_call>\s*(.*?)\s*</tool_call>", raw, flags=re.DOTALL)
|
| 842 |
+
responses = re.findall(r"<tool_response>\n?(.*?)\n?</tool_response>", raw, flags=re.DOTALL)
|
| 843 |
+
cleaned = re.sub(
|
| 844 |
+
r"<tool_call>.*?</tool_response>\s*(?:<\|im_end\|>\s*<\|im_start\|>assistant\s*<think>\s*</think>\s*)?",
|
| 845 |
+
"\n\n",
|
| 846 |
+
raw,
|
| 847 |
+
flags=re.DOTALL,
|
| 848 |
+
)
|
| 849 |
+
cleaned = re.sub(r"<tool_call>.*$", "", cleaned, flags=re.DOTALL)
|
| 850 |
+
cleaned = re.sub(r"</?think>", "", cleaned)
|
| 851 |
+
for stop_s in ("<|im_end|>", "<|endoftext|>", "<|im_start|>"):
|
| 852 |
+
if stop_s in cleaned:
|
| 853 |
+
cleaned = cleaned.split(stop_s, 1)[0]
|
| 854 |
+
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned).strip()
|
| 855 |
+
return {
|
| 856 |
+
"raw_text": raw,
|
| 857 |
+
"tool_calls": calls,
|
| 858 |
+
"tool_responses": responses,
|
| 859 |
+
"final_explanation": cleaned,
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
|
| 863 |
+
def main() -> None:
|
| 864 |
+
ap = argparse.ArgumentParser(description="Run Qwen3.5-Math-GraphCalc in-weights")
|
| 865 |
+
ap.add_argument(
|
| 866 |
+
"--ask",
|
| 867 |
+
default="Graph y = x^3 - 3*x from x = -2 to 2, identify its roots and turning points, and state the local maximum y-value.",
|
| 868 |
+
help="Math or graphing question to solve",
|
| 869 |
+
)
|
| 870 |
+
ap.add_argument("--pure-hf", action="store_true", help="Run un-fused HF Qwen3_5DecoderLayer weights (executor=False)")
|
| 871 |
+
ap.add_argument("--max-new-text", type=int, default=512)
|
| 872 |
+
args = ap.parse_args()
|
| 873 |
+
|
| 874 |
+
runner = CarbonGraphCalcRunner(MODEL_DIR, fast=True, executor=not args.pure_hf)
|
| 875 |
+
res = runner.generate(runner.prompt_ids(args.ask), max_new_text=args.max_new_text, check_lock=True)
|
| 876 |
+
if res["tool_responses"]:
|
| 877 |
+
print("=== IN-WEIGHTS RV32 <tool_response> ===")
|
| 878 |
+
print(res["tool_responses"][0])
|
| 879 |
+
print()
|
| 880 |
+
print("=== STEP-BY-STEP TUTOR EXPLANATION ===")
|
| 881 |
+
print(res["final_explanation"])
|
| 882 |
+
print(f"\n[chip_tokens={res['chip_tokens']} | lock_error={res['lock_error']} | {res['seconds']}s]")
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
if __name__ == "__main__":
|
| 886 |
+
main()
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eeb7adf50a06ace136ccec295ee33e6e88deac189bffab57052415de813a7dcf
|
| 3 |
+
size 64037666
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"audio_bos_token": "<|audio_start|>",
|
| 4 |
+
"audio_eos_token": "<|audio_end|>",
|
| 5 |
+
"audio_token": "<|audio_pad|>",
|
| 6 |
+
"backend": "tokenizers",
|
| 7 |
+
"bos_token": null,
|
| 8 |
+
"clean_up_tokenization_spaces": false,
|
| 9 |
+
"eos_token": "<|endoftext|>",
|
| 10 |
+
"errors": "replace",
|
| 11 |
+
"extra_special_tokens": [
|
| 12 |
+
"<|im_start|>",
|
| 13 |
+
"<|im_end|>",
|
| 14 |
+
"<|object_ref_start|>",
|
| 15 |
+
"<|object_ref_end|>",
|
| 16 |
+
"<|box_start|>",
|
| 17 |
+
"<|box_end|>",
|
| 18 |
+
"<|quad_start|>",
|
| 19 |
+
"<|quad_end|>",
|
| 20 |
+
"<|vision_start|>",
|
| 21 |
+
"<|vision_end|>",
|
| 22 |
+
"<|vision_pad|>",
|
| 23 |
+
"<|image_pad|>",
|
| 24 |
+
"<|video_pad|>"
|
| 25 |
+
],
|
| 26 |
+
"image_token": "<|image_pad|>",
|
| 27 |
+
"is_local": false,
|
| 28 |
+
"local_files_only": true,
|
| 29 |
+
"merges_file": null,
|
| 30 |
+
"model_max_length": 262144,
|
| 31 |
+
"model_specific_special_tokens": {
|
| 32 |
+
"audio_bos_token": "<|audio_start|>",
|
| 33 |
+
"audio_eos_token": "<|audio_end|>",
|
| 34 |
+
"audio_token": "<|audio_pad|>",
|
| 35 |
+
"image_token": "<|image_pad|>",
|
| 36 |
+
"video_token": "<|video_pad|>",
|
| 37 |
+
"vision_bos_token": "<|vision_start|>",
|
| 38 |
+
"vision_eos_token": "<|vision_end|>"
|
| 39 |
+
},
|
| 40 |
+
"pad_token": "<|endoftext|>",
|
| 41 |
+
"pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
|
| 42 |
+
"split_special_tokens": false,
|
| 43 |
+
"tokenizer_class": "Qwen3_5Tokenizer",
|
| 44 |
+
"unk_token": null,
|
| 45 |
+
"video_token": "<|video_pad|>",
|
| 46 |
+
"vision_bos_token": "<|vision_start|>",
|
| 47 |
+
"vision_eos_token": "<|vision_end|>"
|
| 48 |
+
}
|