Install
$ agentstack add skill-horizonrobotics-oe-skills-j6-plugin-insert-quant-dequant ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
给任意浮点模型插入 Quant/DeQuant(Horizon 部署边界版)
目标
把任意 浮点 torch.nn.Module 改造成可被 horizon_plugin_pytorch 量化工具链处理的形式:在部署输入边界插入 QuantStub,在部署输出边界插入 DeQuantStub。
这里必须先区分两类逻辑:
- 部署模块 / 部署子图:推理落板时真正需要保留、需要进入量化图的部分,例如 backbone、neck、head、deploy module 等。
- 非部署模块 / 非部署逻辑:只在训练、评估、可视化、日志或 CPU/浮点后处理中使用,不属于最终部署子图,例如 loss 计算、matcher、target assign、NMS 后处理、结果格式整理、metrics、debug print、可视化等。
QuantStub -> ... -> DeQuantStub 中间这段,应该只包含部署模块。凡是部署时不需要的逻辑,都不应该被夹在 quant 和 dequant 中间。
本 Skill 强约束:
- quant/dequant 是部署边界:标记从哪里开始/结束部署(进入/离开量化图)。
- 每个输入和输出都单独创建 quant/dequant:不要复用同一个 stub 处理多个输入或多个输出。
- QuantStub 初始化不要设置 scale:不要传
scale=...,交给量化流程决定。 - QuantStub 只针对浮点 tensor:只有浮点 tensor 输入/输出才需要按部署边界插入 quant/dequant。
- scalar 或非浮点 tensor 不插入 QuantStub:标量(scalar),以及
bool、整数/索引、已是定点语义的 tensor,不要为了“形式统一”强行插 quant。 - quant 和 dequant 之间只放部署逻辑:loss、训练标签处理、评价指标、前后处理、可视化等非部署逻辑必须放在边界之外。
- train/eval 的边界定义必须一致:不要仅因
self.training为真/假就改变“哪里需要 dequant”的位置;是否dequant取决于后续逻辑是否已经离开部署图,而不是取决于当前处于训练还是评估模式。 - 区分“边界输入”和“图内常量输入”:
QuantStub/DeQuantStub负责标注部署边界;但如果部署图内部存在参与算子计算的 tensor 常量输入(例如x + const_tensor里的const_tensor),也必须把它纳入 quant 语义。标量(scalar)可按算子属性处理,不显式插 quant 节点;tensor 形式的常量输入应视为输入参与量化。
第零步:先判断谁属于“部署边界内”
在插入 QuantStub/DeQuantStub 之前,先把目标代码拆成两段:
A. 可以放在 quant 与 dequant 之间的内容
这些通常属于部署模块:
- 主干网络 / backbone
- neck / encoder / decoder / detection head
- 明确要参与板端推理的特征变换
- 明确属于部署图一部分的张量级算子
B. 不能放在 quant 与 dequant 之间的内容
这些通常属于非部署逻辑:
- loss 计算
- matcher / assigner / target builder
- 训练分支专用监督逻辑
- 输入前处理(如果它不属于模型部署图,而是 dataloader / Python 侧处理)
- 输出后处理(如 NMS、阈值过滤、格式整理、映射回原图)
- COCO evaluator / metrics / logger / visualizer
.cpu()/.numpy()/ Python list/dict 整理 / 画图 / dump 文件
如果某段逻辑在板端部署时不会保留,就不要把它塞进 quant/dequant 边界里。
补充说明:
- “输入”不只指
forward(...)的外部参数,也包括部署图内部某个算子的独立输入。 - 因此,一个由模型内部构造出来的常量
Tensor,只要它作为add/cat/matmul/attention等算子的输入参与部署图计算,就不能把它当成“天然 float 附件”忽略量化。 - 但这不意味着要把它误改成新的部署边界:边界 QuantStub 仍只负责模型 I/O 边界;图内常量
Tensor的重点是“必须进入 quant 语义”,而不是“必须伪装成外部输入”。
标准改法(通用模板)
1) 增加 import
from horizon_plugin_pytorch.quantization import QuantStub
from torch.quantization import DeQuantStub
2) 在 __init__ 中:为每个输入/输出创建独立 stub
情况 A:单输入、单输出
self.quant_x = QuantStub()
self.dequant_y = DeQuantStub()
情况 B:多输入(每个输入一个 QuantStub)
假设签名是 forward(self, x, y, meta=None),其中 x/y 是 tensor,meta 是非 tensor:
self.quant_x = QuantStub()
self.quant_y = QuantStub()
说明:
- 不要把
x/y共同送进同一个self.quant。 - 只有浮点 tensor(如
float16/float32)通常需要量化。 - 非 tensor(如
meta)通常不量化。 - scalar、
booltensor、整型/索引 tensor、已是定点语义的 tensor,通常也不作为QuantStub的对象。
例如:
def forward(self, x, mask, index, scale_factor: float):
x = self.quant_x(x) # x 是浮点 tensor,需要 quant
# mask 是 bool tensor,不插 quant
# index 是整型 tensor,不插 quant
# scale_factor 是 scalar,不插 quant
out = self.net(x, mask=mask, index=index, scale_factor=scale_factor)
...
情况 C:多输出(每个输出一个 DeQuantStub)
假设模型返回 (logits, boxes):
self.dequant_logits = DeQuantStub()
self.dequant_boxes = DeQuantStub()
3) 在 forward 中:只在部署边界调用 stub(且覆盖所有 return 路径)
核心原则:
quant之后立即进入部署模块dequant之后才能进入非部署逻辑- 不要写成“量化 -> loss/后处理/评估 -> 反量化”这种错误边界
情况 A:单输入、单输出
x = self.quant_x(x)
y = self.net(x)
y = self.dequant_y(y)
return y
情况 B:多输入
x = self.quant_x(x)
y = self.quant_y(y)
out = self.net(x, y, meta=meta)
...
情况 C:tuple/list 输出(逐项独立 dequant)
logits, boxes = self.head(feats)
logits = self.dequant_logits(logits)
boxes = self.dequant_boxes(boxes)
return logits, boxes
情况 D:dict 输出(按 key 独立 dequant)
out = {"logits": logits, "boxes": boxes, "features": feats}
out["logits"] = self.dequant_logits(out["logits"])
out["boxes"] = self.dequant_boxes(out["boxes"])
return out
情况 E:部署输出之后还有 float-only 后处理
典型错误写法:
x = self.quant_x(x)
pred = self.head(x)
pred = self.nms_postprocess(pred) # 错:如果这不是部署图的一部分,就不该夹在中间
pred = self.dequant_pred(pred)
return pred
正确思路是先明确部署输出边界,再离开量化图:
x = self.quant_x(x)
pred = self.head(x)
pred = self.dequant_pred(pred)
pred = self.nms_postprocess(pred) # 对:非部署逻辑放在边界外
return pred
情况 F:训练时有 loss,部署时只要预测结果
典型错误写法:
x = self.quant_x(x)
pred = self.head(x)
loss = self.criterion(pred, targets) # 错:loss 不属于部署图
pred = self.dequant_pred(pred)
return pred, loss
正确思路:
x = self.quant_x(x)
pred = self.head(x)
pred = self.dequant_pred(pred)
if self.training:
loss = self.criterion(pred, targets)
return pred, loss
return pred
也就是说:
criterion / loss / matcher这类逻辑应在dequant之后,或者更外层的训练引擎里完成- 不要为了“图方便”把训练专用逻辑留在部署边界内部
进一步强调:
- 如果训练态下,模型输出会立刻送去
criterion / matcher / assigner / metrics / Python 后处理,那么这些输出在训练态同样应该先 dequant。 - 不要写成“评估时 dequant,训练时不 dequant”的分裂边界,除非你能明确证明训练态返回值仍然继续留在部署图内部、还会被后续量化模块使用。
典型错误写法:
x = self.quant_x(x)
pred = self.head(x)
if not self.training:
pred = self.dequant_pred(pred)
return pred
上面这段的问题是:
- 评估态把
pred视作部署边界输出; - 训练态却又把
pred留在量化图内部; - 于是同一个返回值在 train/eval 下拥有两套不同的边界定义。
如果 pred 在训练时马上就会送入 loss/matcher,那么这也是错误边界。
更合理的写法通常是:
x = self.quant_x(x)
pred = self.head(x)
pred = self.dequant_pred(pred)
if self.training:
loss = self.criterion(pred, targets)
return pred, loss
return pred
或者如果 criterion 在模型外:
x = self.quant_x(x)
pred = self.head(x)
pred = self.dequant_pred(pred)
return pred
情况 G:图内部的常量 tensor 输入
假设模型内部存在:
def fun(x, const_tensor):
return x + const_tensor
其中 const_tensor 虽然不是用户从 forward(...) 外部传进来的,但它是 add 的一个 tensor 输入。这种场景应遵守:
- 如果
const_tensor是 scalar 常量(如x + 1.0),通常可按 scalar 处理,不一定显式插 quant 节点。 - 如果
const_tensor是 Tensor 常量输入(如 shape 为[1, H*W, C]的位置编码),则应纳入 quant 处理,不能直接把量化 tensor 与裸 float tensor 混用。
换句话说:
- 它不一定是部署边界输入;
- 但它严格来说仍然是算子输入;
- 因此 浮点 tensor 形式的常量输入也要量化。
- 如果该常量只是 scalar,或者本身是
bool/ 整型 / 已有定点语义的 tensor,则通常不需要额外插QuantStub。
4) 多分支/早返回:每条 return 都要符合部署边界定义
如果有:
if return_features: return featsif not include_top: return feats_list
你必须明确这些返回值是否属于“部署输出边界”:
- 如果是部署输出:每个输出分别
DeQuantStub()。 - 如果不是部署输出(仍在部署内部,供后续量化模块消费):不要 dequant,保持量化态。
补充判断标准:
- 如果返回值马上还要进入 loss、评估器、可视化、Python 后处理,这通常说明这里已经到了部署边界外,应先
dequant - 如果返回值还要继续流入另一个部署模块,则这里仍在部署边界内,不应提前
dequant
约定与命名建议(便于审查)
- 输入量化 stub:
quant_(如quant_x、quant_img、quant_points) - 输出反量化 stub:
dequant_(如dequant_logits、dequant_boxes) - 如果输出是
list且有固定语义:用语义命名多个 dequant(不要dequant0/dequant1,除非确实没有语义名)。
常见坑(按本 Skill 直接规避)
- 不要复用 stub:一个
QuantStub同时量化多个输入,或一个DeQuantStub同时反量化多个输出,都会让部署边界不清晰且不符合本规范。 - 不要设置 QuantStub(scale=...):这里强制不传 scale。
- 不要见到 tensor 就一律 quant:只有浮点 tensor 才是
QuantStub的目标;scalar、booltensor、整型/索引 tensor、已是定点语义的 tensor,不要强行插 quant。 - 不要把 dequant 放太早:只在你要离开部署/量化图时 dequant(例如进入 float-only 后处理、numpy/CPU 逻辑、显示/导出等)。
- 不要用
self.training直接决定是否 dequant:dequant的依据应是“后续是否进入部署边界外逻辑”,而不是“当前是不是训练模式”。 - 不要把非部署逻辑放在 quant/dequant 中间:例如 loss、matcher、后处理、metrics、可视化、数据格式转换等。
- 不要把“训练需要”误认为“部署需要”:训练图里会出现的逻辑,不代表就应该保留在部署量化边界内。
- 不要把 dataloader/数据预处理硬塞进量化图:如果它本来是模型外的 Python 逻辑,就应该继续放在边界外。
- 不要把图内浮点 tensor 常量输入误判成“无需量化的 float 附件”:像位置编码、常量 bias tensor、参与
cat/add/matmul的固定 tensor,只要是浮点 tensor 且作为算子输入,就应纳入 quant 语义;只有 scalar 常量,或bool/整型/已定点语义 tensor,才通常不需要单独插 quant 节点。 - 不要把图内 tensor 常量输入误改造成新的模型边界:它需要量化,不代表它必须被重构成新的
forward(...)外部输入;仍应优先保持在部署图内部处理。
快速自检清单
__init__中:每个部署输入浮点 tensor 都有独立的QuantStub();每个部署输出浮点 tensor 都有独立的DeQuantStub()。- scalar、
booltensor、整型/索引 tensor、已是定点语义的 tensor,是否避免了误插QuantStub()? - 是否已经明确区分:哪些模块属于部署、哪些逻辑属于非部署?
QuantStub和DeQuantStub中间,是否只包含部署时真正需要保留的模块?- 部署图内部若存在常量 浮点 Tensor 形式的算子输入,是否已经确保它进入 quant 语义,而不是与量化 tensor 直接做 float 混算?
- loss / matcher / 前处理 / 后处理 / evaluator / 可视化 / numpy 转换,是否都在边界之外?
forward中:所有 return 路径都严格遵守同一套部署边界定义。- train / eval 下是否对同一类返回值使用了一致的边界定义,而不是一边
dequant、一边不dequant? QuantStub()初始化无scale参数。
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: HorizonRobotics
- Source: HorizonRobotics/OE-Skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.