LM Studio 报 Unknown StringValue filter: safe?一个脚本原地修复 GGUF

本文最后更新于 2026年7月12日 晚上

下载了个新模型,LM Studio 一跑就报错。折腾半天,最后发现只需要改一个单词。

翻车现场

最近在模型服务器上部署 Qwen3.6-35B-A3B-Uncensored,模型下好了,LM Studio 加载也正常,结果一发消息就炸了:

1
2
3
4
5
6
7

1
2

AI_ProviderSpecificError: Unknown StringValue filter: safe
Error rendering prompt with jinja template: "Unknown StringValue filter: safe"

模型明明没问题,为什么连 prompt 都渲染不了?

根因定位

GGUF 文件里内嵌了一段 Jinja 模板(chat_template),负责把对话历史转成模型能理解的文本格式。问题出在这行:

1
2
3
4
5

1

{%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %}

模型作者用了 | safe 这个 Jinja2 过滤器。| safe 在 Web 开发里很常见——告诉模板引擎”这段 HTML 别转义”。但在 LLM prompt 渲染里完全没必要。

关键是:LM Studio 用的精简版 Jinja 引擎不支持 | safe 直接就报错了。

这不是模型的 bug,是 LM Studio 的兼容性问题。

三条路,两条走不通

❌ 方案一:改缓存

LM Studio 会把模型元数据缓存到 gguf-metadata-cache.json。我直接改这个文件,删掉 | safe

有效——但重启 LM Studio 后缓存重建,问题复现。

❌ 方案二:model.yaml 覆盖

LM Studio 支持 model.yaml 配置文件,理论上可以覆盖内置模板。

实际:写进去了,LM Studio 压根不认。可能是版本问题。

✅ 方案三:直接改 GGUF 文件

最暴力,也最彻底。但 19GB 的文件,全读全写?

关键发现:| safe| trim 都是 6 个字符。

等长替换,不需要移动任何数据,直接在二进制里原地改就行。文件大小不变,GGUF 结构不变。

为什么用 | trim 替换

| 对比项 | | safe | | trim |
|——–|———-|———-|
| 字符数 | 6 | 6 |
| Jinja 标准 | ✅ | ✅ |
| LM Studio 支持 | ❌ | ✅ |
| 对 JSON 输出的影响 | 标记不转义 | 去首尾空白 |

| trim 去除字符串首尾空白,对 tojson 的输出不会有任何副作用。完美的等价替换。

修复脚本

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

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

#!/usr/bin/env python3
"""
修复 GGUF 文件中的 chat_template - 将 | safe 替换为 | trim
等长替换,不改变文件大小,直接原地修改。
"""
import sys
import os
import struct

def main():
if len(sys.argv) < 2:
print("Usage: python3 fix_gguf_safe.py <gguf_file>")
sys.exit(1)

gguf_path = sys.argv[1]

if not os.path.exists(gguf_path):
print(f"Error: File not found: {gguf_path}")
sys.exit(1)

print(f"Reading: {gguf_path}")
print(f"File size: {os.path.getsize(gguf_path) / (1024**3):.2f} GB")

# 读取整个文件到内存
with open(gguf_path, 'rb') as f:
data = bytearray(f.read())

# 定位 tokenizer.chat_template 键
key_marker = b'tokenizer.chat_template'

try:
key_idx = data.index(key_marker)
except ValueError:
print("Error: tokenizer.chat_template not found in file")
sys.exit(1)

# 解析 GGUF 结构,找到 chat_template 的值区域
# GGUF 结构: [key_length(8B)][key][value_type(4B)][value_length(8B)][value]
key_len = struct.unpack('<Q', bytes(data[key_idx-8:key_idx]))[0]
type_off = key_idx + key_len
val_len_off = type_off + 4
val_len = struct.unpack('<Q', bytes(data[val_len_off:val_len_off+8]))[0]
val_start = val_len_off + 8
val_end = val_start + val_len

# 读取原始模板
original_template = bytes(data[val_start:val_end]).decode('utf-8')
print(f"chat_template: offset={val_start}, length={val_len}")

# 检查是否需要修复
safe_count = original_template.count('| safe')
if safe_count == 0:
print("✅ Template is already clean (no | safe found)")
return

print(f"Found {safe_count} occurrence(s) of '| safe', replacing with '| trim'...")

# 等长替换 | safe → | trim
fixed_template = original_template.replace('| safe', '| trim')
fixed_bytes = fixed_template.encode('utf-8')
original_bytes = original_template.encode('utf-8')

# 安全检查:长度必须一致
if len(fixed_bytes) != len(original_bytes):
print(f"ERROR: Length mismatch! Original: {len(original_bytes)}, Fixed: {len(fixed_bytes)}")
sys.exit(1)

# 原地替换
data[val_start:val_end] = fixed_bytes

# 写回文件
with open(gguf_path, 'wb') as f:
f.write(data)

print(f"✅ Successfully patched: {gguf_path}")
print(f" Replaced {safe_count} occurrence(s) of '| safe' → '| trim'")

if __name__ == '__main__':
main()

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

1
2
3
4
5
6
7
8

# 1. 运行修复脚本
python3 fix_gguf_safe.py /path/to/your/model.gguf

# 2. 清理 LM Studio 缓存(重要!)
rm -f ~/.lmstudio/.internal/gguf-metadata-cache.json

# 3. 重启 LM Studio
systemctl restart lmstudio

实际效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

1
2
3
4
5
6

Reading: Qwen3.6-35B-A3B-Uncensored-Q4_K_M.gguf
File size: 19.71 GB
chat_template: offset=10934766, length=7764
Found 1 occurrences of '| safe', replacing with '| trim'...
✅ Successfully patched!
Replaced 1 occurrence(s) of '| safe''| trim'

19GB 的文件,不到一分钟搞定。

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1
2
3
4
5
6
7

# 确认没有残留的 | safe
strings model.gguf | grep -o '| safe' | wc -l
# 输出: 0

# 确认 | trim 已替换
strings model.gguf | grep 'tojson | trim'
# 输出: ... args_value | tojson | trim ...

以后怎么预防

下载新模型后先查一下:

1
2
3
4
5

1

strings model.gguf | grep '| safe' && echo "⚠️ 需要修复" || echo "✅ 兼容"

或者优先选择 lmstudio-community 发布的模型版本——他们已经做过兼容性处理。

总结

方案
效果
持久性

改缓存
有效但不稳定
❌ 重启失效

model.yaml
不一定生效
⚠️ 看版本

改 GGUF
彻底解决
✅ 永久

本质就是:模型作者用了一个 LM Studio 不支持的 Jinja 过滤器。等长字符串替换,原地改一个单词,问题消失。


参考资料GGUF 格式规范 · Jinja2 模板引擎 · LM Studio 官方文档


LM Studio 报 Unknown StringValue filter: safe?一个脚本原地修复 GGUF
https://www.normdist.com/2026/05/20/ND-20260520-002-lmstudio-gguf-safe-filter-fix/
作者
小瑞
发布于
2026年5月20日
许可协议