File size: 13,847 Bytes
f0079cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b497cd9
 
f0079cc
 
 
 
 
 
 
 
b497cd9
f0079cc
 
 
 
 
 
 
 
 
 
 
 
646980b
 
 
 
 
 
b497cd9
646980b
 
 
 
 
b497cd9
 
 
f0079cc
 
 
 
 
 
 
 
 
 
 
 
b497cd9
 
f0079cc
 
b497cd9
f0079cc
b497cd9
f0079cc
b497cd9
f0079cc
b497cd9
f0079cc
b497cd9
f0079cc
b497cd9
f0079cc
b497cd9
 
f0079cc
 
b497cd9
 
 
 
 
ee6912b
f0053c9
 
 
 
 
 
 
 
 
 
 
ee6912b
 
 
f0053c9
ee6912b
f0053c9
ee6912b
 
646980b
e7e5dc3
f0053c9
 
ee6912b
 
 
 
646980b
 
e7e5dc3
 
 
b497cd9
e7e5dc3
 
 
 
 
 
 
 
ee6912b
f0053c9
f0079cc
 
 
 
 
 
 
 
 
 
 
 
 
b497cd9
 
 
 
 
 
f0079cc
 
b497cd9
 
f0079cc
 
 
 
 
 
 
 
b497cd9
f0079cc
 
b497cd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f0079cc
 
b497cd9
 
f0079cc
b497cd9
 
 
 
 
 
 
 
f0079cc
646980b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b497cd9
646980b
 
 
 
 
f0079cc
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
---
language:
  - en
  - zh
base_model: openpangu/openPangu-R-7B-Diffusion
tags:
  - gguf
  - llama.cpp
  - diffusion
  - block-diffusion
  - openPangu
license: other
---

# openPangu-R-7B-Diffusion β€” Q4_K_M GGUF (llama.cpp)

Quantized **Q4_K_M** GGUF of [`openpangu/openPangu-R-7B-Diffusion`](https://huggingface.co/openpangu/openPangu-R-7B-Diffusion),
together with a llama.cpp patch that adds native support for the model's
**context-causal block-diffusion** decoding.

> `openPangu-R-7B-Diffusion` is a 7B block-diffusion LLM. Generation is *not* plain
> next-token autoregression: a block of tokens (default length **32**) is denoised in
> parallel with **full (bi-directional) attention inside the block** and **causal
> attention for the preceding context**, revealing the most confident tokens first.
> The model can also be run in ordinary autoregressive mode.

Upstream llama.cpp (as of tag `b11719`) ships the `PanguEmbedded` architecture
(used by the autoregressive `openPangu-Embedded`) but does **not** implement the
diffusion decoding this model needs, hence the patch.

## Files

| File | Description |
|---|---|
| `pangu-diffusion-Q4_K_M.gguf` | Q4_K_M quantized weights (~4.6 GB), ready to run |
| `pangu-diffusion-llamacpp.patch` | llama.cpp patch adding openPangu-R-Diffusion support |
| `build_llamacpp_pangu_diffusion.sh` | one-click script: clone llama.cpp β†’ apply patch β†’ build |

## Quick start

### 1. Build llama.cpp with the patch (one-click)

```bash
bash build_llamacpp_pangu_diffusion.sh
# binaries end up in ./pangu-diffusion-build/llama.cpp/build/bin/
```

Or manually:

```bash
git clone --depth 1 --branch b11719 https://github.com/ggml-org/llama.cpp.git
cd llama.cpp
git apply /path/to/pangu-diffusion-llamacpp.patch
cmake -B build -DGGML_NATIVE=ON -DLLAMA_CURL=OFF
cmake --build build -j --target llama-diffusion-cli llama-cli llama-quantize
```

### 2. Diffusion decoding (recommended)

`llama-diffusion-cli` applies the model's chat template automatically: pass the **raw
user message**, not a pre-formatted prompt. The rendered prompt is

```
[unused9]系统:<system>[unused10][unused9]η”¨ζˆ·οΌš<user>[unused10][unused9]εŠ©ζ‰‹οΌš
```

```bash
./build/bin/llama-diffusion-cli \
  -m pangu-diffusion-Q4_K_M.gguf \
  -p "Which is bigger, 9.11 or 9.9?" \
  -ub 256 \
  --diffusion-block-length 32 \
  --diffusion-small-block 4 \
  --diffusion-algorithm 1 \
  --temp 0 -t 16 --seed 42
```

Recommended sampling (from the official model card):
`alg = entropy`, `num_small_blocks = 8` β†’ `--diffusion-small-block 4` (with block length 32),
`top_p = 1`, `temperature = 0`. For coding tasks the authors suggest
`alg = entropy, num_small_blocks = 32 (β†’ --diffusion-small-block 1), top_p = 0.8, temperature = 1`.

**Fast mode** (higher throughput): reveal every masked token whose confidence exceeds a
threshold in a single denoising step, with one small block per block:

```bash
./build/bin/llama-diffusion-cli \
  -m pangu-diffusion-Q4_K_M.gguf \
  -p "<question>" \
  -ub 256 --diffusion-block-length 32 --diffusion-small-block 32 \
  --diffusion-confidence-threshold 0.9 --diffusion-algorithm 4 \
  --temp 0 -t 16 --seed 42
```

`--diffusion-n-seq N` runs N sequences through the block decoder in a single
(micro-)batch (the prompt is replicated; see *Batched decoding* below).

`--diffusion-algorithm` values: `0` origin, `1` entropy, `2` margin, `3` random, `4` confidence.
`-ub N` is the maximum sequence length; the tail that does not fill a whole block is dropped.

### 3. Autoregressive decoding

```bash
./build/bin/llama-cli -m pangu-diffusion-Q4_K_M.gguf \
  -p "[unused9]系统:[unused10][unused9]η”¨ζˆ·οΌšHi![unused10][unused9]εŠ©ζ‰‹οΌš" -n 128 -t 16
```

## Example

Prompt (translated): *β€œWhich is bigger, 9.11 or 9.9?”* β€” command as in Β§2, fast mode
(`-ub 256`, `--diffusion-confidence-threshold 0.9`, Q4_K_M):

```
I need to figure out which is bigger between 9.11 and 9.9. Both are decimals, so I should compare them directly.

First, let's write them with the same number of decimal places to make it easier. 9.11 has two decimal places, and 9.9 has one. So, I can write 9.9 as 9.90 to make it two decimal places.

So, 9.11 vs 9.90.

Now, comparing the tenths place: both have 9 in the tenths place. So, that's the same.

Next, the hundredths place: 9.11 has 1 in the hundredths place, and 9.90 has 0 in the hundredths place. Since 1 is greater than 0, ...

I can think of them as fractions. 9.11 is 911/100, and 9.9 is 990/100.

Since the denominator is the same, I can compare the numerators: 911 vs 990.
911 is less than 990, so 911/100 is less than 990/100, which ...
```

For a short prompt the output is reproduced **token-for-token identically to the Hugging
Face reference** (see *Numerical parity* below), e.g. `-p "hello"` (f16 GGUF):
`[unused16]Okay, the user just said "hello". That's a simple greeting. I should respond
politely and warmly. Maybe they're just starting a conversation`.

## Speed when you actually use it (measured)

Test environment β€” the resources **actually usable inside the container (docker)**:

| Resource | Value |
|---|---|
| CPU | Intel Xeon Platinum 8559C |
| Effective cores | **16 vCPU** (cgroup `cpu.max` = `1600000 100000`) |
| Memory (cgroup limit) | **β‰ˆ 97 GiB** |
| GPU | **none usable** β€” `/dev/nvidia*` nodes exist but `cuInit()` returns `CUDA_ERROR_NO_DEVICE` and NVML init fails |
| Build / runtime | CPU-only, `-t 16` |

These are **end-user speeds** measured from real runs of the CLI on
`pangu-diffusion-Q4_K_M.gguf` (4.59 GiB, 8.03 B params), i.e. what you actually
experience while using it:

| What the user experiences | Mode | Speed |
|---|---|---|
| **Input** β€” waiting for the prompt to be read before output starts | both | **β‰ˆ 78 t/s** short prompt; **β‰ˆ 132 t/s** for longer prompts (`llama-bench` pp512) |
| **Output** β€” text generated (autoregressive, streamed live) | `llama-cli` | **11.9 t/s** (llama-bench tg128: 11.1 t/s) |
| **Output** β€” block diffusion (quality: block 32 / small-block 4, `alg=entropy`, `temp=0`) | `llama-diffusion-cli` | **β‰ˆ 4.4 t/s** |
| **Output** β€” block diffusion (fast: `--diffusion-confidence-threshold 0.9`, small-block 32, `alg=confidence` β€” the official throughput setting) | `llama-diffusion-cli` | **β‰ˆ 5 t/s** |

Notes:
- Input (prompt processing) is the same causal prefill for both paths; it is what you wait
  for before the answer appears.
- The AR output speed (β‰ˆ 12 t/s) is what `llama-cli` prints as `Generation: X t/s`; the
  diffused text is only flushed at the end of a run (unless `--diffusion-visual`).
- Block diffusion normally reveals one token per denoising step, i.e. up to `block_length`
  (= 32) forward passes per block. With `--diffusion-confidence-threshold T` every masked
  token whose confidence exceeds `T` is revealed in the same step (`transfer = argmax(conf)
  | (conf > T)`), which raises throughput β€” the same mechanism the authors report as giving
  up to 2.5Γ— over autoregressive decoding on Ascend NPUs. A lower `T` is faster but less
  accurate; measured on this CPU (235 tokens, block 32 / small-block 32, `alg=confidence`,
  `temp=0`):

  | `--diffusion-confidence-threshold` | speed |
  |---|---|
  | `0.9` (default) | 4.9 t/s |
  | `0.7` | 7.4 t/s |
  | `0.5` | 8.7 t/s |

- The AR figures are the best reference for raw model throughput on this hardware.

## What the patch does

1. **`src/llama-arch.cpp`** β€” mark `LLM_ARCH_PANGU_EMBED` as a diffusion architecture
   (`llm_arch_is_diffusion()`), so `llama-diffusion-cli` accepts the model.
2. **`examples/diffusion/diffusion.{h,cpp}`** β€” add a *context-causal block-diffusion*
   decoder (`diffusion.block_attention`):
   - prefill the prompt with a causal forward (kept in the KV cache);
   - denoise each block with `causal_attn = false` β†’ full attention over the cached
     context **plus** the current block (the KV-cache kq mask keeps every non-empty
     cached cell when non-causal);
   - re-decode the block each step, dropping its stale keys (`llama_memory_seq_rm`),
     reveal the most-confident masked token of the current small block, left to right;
   - commit the block with a causal forward so the KV cache holds correct causal
     context, then sample the first token of the next block from the last position;
   - `diffusion_generate_nseq()` generalises this to `n_seq` sequences sharing one
     micro-batch (see `--diffusion-n-seq`).
3. **`examples/diffusion/diffusion-cli.cpp`** β€” read `diffusion.block_attention`, add
   `--diffusion-small-block/-confidence-threshold/-n-seq`, size the context for the
   batched prefill, and use greedy sampling when `temperature == 0`.
4. **`gguf-py` + `conversion/pangu.py`** β€” write the new `diffusion.block_attention`
   metadata key for diffusion checkpoints (alongside `diffusion.shift_logits` and
   `tokenizer.ggml.mask_token_id`), and set `tokenizer.ggml.add_space_prefix = false`
   (see *Tokenizer fix* below).

### Model metadata in the GGUF

```
general.architecture            = pangu-embedded
diffusion.shift_logits          = true
diffusion.block_attention       = true
tokenizer.ggml.mask_token_id    = 45830
tokenizer.ggml.add_space_prefix = false
```

### Tokenizer fix

The upstream conversion honours `add_prefix_space` from `tokenizer_config.json`, but the
slow `PanguTokenizer` (sentencepiece, `legacy: true`) does **not** actually prepend a space.
With the flag left on, llama.cpp inserted spurious space tokens after every special token
(`[unused9]η³»η»ŸοΌšβ€¦` became `[unused9]β η³»η»ŸοΌšβ€¦`), which changed the input and the generation.
The patch forces `tokenizer.ggml.add_space_prefix = false`; the tokenizer output then
matches the reference exactly (a 22-token prompt becomes 22 tokens, was 37).

### Numerical parity

Validated against the Hugging Face reference (`inference/generation_utils.py`,
`alg=confidence`, `num_small_blocks=1`, `threshold=0.9`, `temperature=0`) on the **f16**
GGUF: for `-p "hello"` the generated token IDs are **identical, token for token**, to the
reference. (The earlier mismatch was entirely the tokenizer issue above, not the
decoder.)

## Batched decoding (`--diffusion-n-seq`)

`--diffusion-n-seq N` runs N independent sequences through the block decoder inside a
single (micro-)batch. The example CLI replicates the prompt, so this is mainly a way to
measure batching; a real deployment would fill the rows with different prompts.

All sequences produce **byte-identical output** to the single-sequence run (verified for
N = 4 and N = 8), i.e. the batching is correct.

Measured on the CPU above (Q4_K_M, block 32 / small-block 32, `threshold 0.9`, greedy,
235 generated tokens):

| `--diffusion-n-seq` | wall time | aggregate | per sequence |
|---|---|---|---|
| 1 | 44.5 s | 5.28 t/s | 5.28 t/s |
| 4 | 162.4 s | 5.79 t/s | 1.45 t/s |
| 8 | 321.2 s | 5.85 t/s | 0.73 t/s |

**Review / honest verdict:** on this **CPU** batching gives essentially *no* throughput
gain β€” aggregate speed stays flat (β‰ˆ5.3–5.9 t/s) and the time grows ~linearly with `N`,
so per-sequence speed drops 1/N. That is expected: a CPU decode step is compute-bound and
widens ~linearly with `n_seq` (there are no tensor cores to fill and no memory-bandwidth
headroom to exploit), so the fixed per-step work simply multiplies. Batching is where the
official 2.5Γ— over autoregressive decoding comes from **on Ascend NPUs/GPUs**, not here.
We kept it because it is correct, cheap to carry, and a prerequisite for any GPU
deployment β€” but do not expect a CPU speed-up from it.

## Notes / limitations

- CPU-only, `-t 16`, β‰ˆ 4.6 GB file: a 256-token block-diffusion run takes tens of seconds
  to a few minutes (`block_length` forwards per block). Throughput can be improved with
  `alg = confidence_threshold, num_small_blocks = 1` (see the official README).
- The decoder reproduces the Hugging Face reference **token-for-token** on the f16 GGUF
  (see *Numerical parity*), and the block-diffusion path was cross-checked against the
  reference logits (`cos β‰ˆ 0.996`, f16 vs bf16).
- **Quantization + the greedy fast mode can change the reasoning**: on the adversarial
  `9.11 vs 9.9` prompt the Q4_K_M fast mode first states the wrong intermediate answer and
  only corrects itself from the fraction comparison above, whereas the f16 model matches
  the reference. Use the f16 GGUF, or `alg = entropy`, `temp = 0`, when a case must be
  exactly reproduced.

## Supported features

Implemented (matching the reference `inference/generation_utils.py`):

| Feature | Status |
|---|---|
| Context-causal block diffusion β€” full attention inside a block, causal attention over the preceding context | βœ“ |
| Block-by-block decoding (block length 32) | βœ“ `--diffusion-block-length` |
| Committed-block KV cache with a causal mask; first token of the next block decoded from the last position | βœ“ |
| Small blocks inside a block (`num_small_blocks`, optimum 4 or 8) | βœ“ `--diffusion-small-block` |
| Variable-length inference / flexible context | βœ“ |
| Logit shift (`diffusion.shift_logits`) | βœ“ |
| Dedicated mask token (`tokenizer.ggml.mask_token_id = 45830`) | βœ“ |
| Reveal order: `confidence`, `entropy`, `topk_margin` | βœ“ `--diffusion-algorithm 4/1/2` |
| `confidence_threshold` fast mode (multi-token reveal per step) | βœ“ `--diffusion-confidence-threshold` |
| EOS early stop | βœ“ |
| Batched (multi-sequence) block diffusion | βœ“ `--diffusion-n-seq` |
| Autoregressive decoding | βœ“ `llama-cli` |

Not implemented:
- Vendor NPU kernels β€” this uses whatever backend llama.cpp was built with (CPU here).

## Original model

- Weights: [`openpangu/openPangu-R-7B-Diffusion`](https://huggingface.co/openpangu/openPangu-R-7B-Diffusion)
- Architecture details, benchmarks and citation: see the original model card.