Instruction Tuning for Secure Code Generation, ICML’24
构建数据集,提出了SafeCoder方法,用Instruction Tuning来训练LM,使其能生成secure and functional correct的代码
SafeCoder
- Standard Instruction Tuning
D s t d D^{std} Dstd是instruction tuning的数据集,每个样本 ( i , o ) (i, o) (i,o)由一个指令 i i i和对应的输出 o o o组成。标准的instruction tuning是fine-tune模型,让模型在给出 i i i的基础上,生成 o o o,对应的negative log-likelihood loss(负对数似然损失)是:
L std ( i , o ) = − log P ( o ∣ i ) = − ∑ t = 1 ∣ o ∣ log P ( o t ∣ o < t , i ) L^{\text{std}}(i, o) = - \log P(o \mid i) = - \sum_{t=1}^{|o|} \log P(o_t \mid o_{<t}, i) Lstd(i,o)=−logP(o∣i)=−∑t=1∣o∣logP(ot∣o<t,i)
- Security Instruction Tuning
D s e c D^{sec} Dsec由三元祖样本 ( i , o s e c , o v u l ) (i, o^{sec}, o^{vul}) (i,osec,ovul)组成,其中 i i i为指令,它指定了编程任务要的功能需求。 o s e c o^{sec} osec和 o v u l o^{vul} ovul分别对应包含和不包含vulnerabilities的完成代码,它们只在security上有区别,其他都是一样的。
security fine-tuning只关注security-related tokens of o s e c o^{sec} osec和 o v u l o^{vul} ovul,通过计算token-level下 o s e c o^{sec} osec和 o v u l o^{vul} ovul的区别,可以找到security-related tokens。构造一个mask向量 m s e c m^{sec} msec, m s e c m^{sec} msec和o^{sec}长度相同,每个元素都为0或1:当 o s e c o^{sec} osec中 t t t位置的token o t s e c o_t^{sec} otsec为security-related token时, m t s e c m_t^{sec} mtsec为1,反之为0。同样的,通过 o v u l o^{vul} ovul,可以得到 m v u l m^{vul} mvul。
A masked negetive log-likelihood loss L s e c L^{sec} Lsec
L sec ( i , o s e c , m s e c ) = − ∑ t = 1 ∣ o s e c ∣ m t s e c ⋅ log P ( o t s e c ∣ o < t s e c , i ) L^{\text{sec}}(i, o^{sec}, m^{sec}) = - \sum_{t=1}^{|o^{sec}|} m_t^{sec} \cdot \log P(o_t^{sec} \mid o_{<t}^{sec}, i) Lsec(i,osec,msec)=−∑t=1∣osec∣mtsec⋅logP(otsec∣o<tsec,i)
A maksed negative log-unlikelihood loss L v u l L^{vul} Lvul
L vul ( i , o v u l , m v u l ) = − ∑ t = 1 ∣ o v u l ∣ m t v u l ⋅ log ( 1 − P ( o t v u l ∣ o < t v u l , i ) ) L^{\text{vul}}(i, o^{vul}, m^{vul}) = - \sum_{t=1}^{|o^{vul}|} m_t^{vul} \cdot \log (1 - P(o_t^{vul} \mid o_{<t}^{vul}, i)) Lvul(i,ovul,mvul)=−∑t=1∣ovul∣mtvul⋅log(1−P(otvul∣o<tvul,i))
L s e c L^{sec} Lsec和 L v u l L^{vul} Lvul使用 m s e c m^{sec} msec和 m v u l m^{vul} mvul来mask仅和security有关的部分,通过最小化 L s e c L^{sec} Lsec和 L v u l L^{vul} Lvul,能够让LLM输出更安全的code。
- 结合Standard Instruction Tuning和Security Tuning
训练的时候,如果样本来自 D s t d D^{std} Dstd,则用标准 L s t d L^{std} Lstd,如果来自 D s e c D^{sec} Dsec,则用security tuning
Dataset Construction
- 用lightweight heuristics, such as keyword matching,选择commits likely to fix vulnerabilities。keywords和considered CWE相关,额外还要求checks the changes within the commit, excluding unsupported file types and commits that edit too many lines and files。规定changes不超过40lines和2个files,这样可以尽量保证changes只和security相关(基于假设:The underlying assumption is that too many changes typically indicate functional edits or refactorings.)
- 用CodeQL检测vulnerability,如果前一个version含有vulnerability,后一个version不含,则commit被认为是a vulnerability fix。对于这个commit,before version被认为是 o v u l o^{vul} ovul,post version被认为是 o s e c o^{sec} osec
- 基于 ( o s e c , o v u l ) (o^{sec}, o^{vul}) (osec,ovul)query GPT-4生成对应的指令 i i i
Large Language Models for Code: Security Hardening and Adversarial Testing, CCS’23
通过security hardening和adversarial testing,实现controlled code generation
Controlled Code Generation: SVEN
不涉及重新训练模型,类似于prefix tuning。在原始的prompt之外,额外一个property
c
c
c,
c
=
{
s
e
c
,
v
u
l
}
c = \{sec, vul\}
c={sec,vul}。如果
c
=
s
e
c
c = sec
c=sec,则进行security hardening,否则,进行adversarial testing。
1. Inference
其实就是prefix tuning。把SVEN当成独立的模块(其实就是向量),只需要更新训练向量,不涉及LM的训练
2. Training
和SafeCoder类似,对于一个training sample
x
x
x,创建一个长度相同的向量
m
m
m,如果
x
t
x_t
xt在被改变的区域,则将
m
t
m_t
mt设为1,否则设为0。
有三种diff level:program,line和character。在这三种level里,character肯定最准确,但是如果fix vulnerability只涉及在before version里加上一些代码,那么在
c
=
v
u
l
c = vul
c=vul的时候,
m
m
m中的全部元素皆为0,没办法进行训练。因此,采用了混合策略,当
c
=
s
e
c
c = sec
c=sec时,使用character-level,当
c
=
v
u
l
c=vul
c=vul时,采用line-level。
因此,数据集中的一个样本是三元组
(
x
,
m
,
c
)
(x, m, c)
(x,m,c),c代表property。相对应的有
¬
c
\neg c
¬c。
L LM = − ∑ t = 1 ∣ x ∣ m t ⋅ log P ( x t ∣ h < t , c ) L_{\text{LM}} = - \sum_{t=1}^{|x|} m_t \cdot \log P(x_t \mid h_{<t}, c) LLM=−∑t=1∣x∣mt⋅logP(xt∣h<t,c)
这个公式用来鼓励SVEN生成含有 c c c属性的样本 x x x。
L C T = − ∑ t = 1 ∣ x ∣ m t ⋅ log ( P ( x t ∣ h < t , c ) P ( x t ∣ h < t , c ) + P ( x t ∣ h < t , ¬ c ) ) L_{CT} = - \sum_{t=1}^{|x|} m_t \cdot \log \left( \frac{P(x_t | h_{<t}, c)}{P(x_t | h_{<t}, c) + P(x_t | h_{<t}, \neg c)} \right) LCT=−∑t=1∣x∣mt⋅log(P(xt∣h<t,c)+P(xt∣h<t,¬c)P(xt∣h<t,c))
L C T L_{CT} LCT用于对比由不同前缀生成的序列的条件下一个令牌的概率。这个公式用来鼓励生成具有特定属性 c c c的序列,同时避免生成具有相反属性 ¬ c \neg c ¬c的序列。
L K L = ∑ t = 1 ∣ x ∣ ( ¬ m t ) ⋅ KL ( P ( x t ∣ h < t , c ) ∣ P ( x t ∣ h < t ) ) L_{KL} = \sum_{t=1}^{|x|} ( \neg m_t ) \cdot \text{KL}( P(x_t | h_{<t}, c) | P(x_t | h_{<t}) ) LKL=∑t=1∣x∣(¬mt)⋅KL(P(xt∣h<t,c)∣P(xt∣h<t))
用 L K L L_{KL} LKL损失来计算 P ( x t ∣ h < t , c ) P(x_t | h_{<t}, c) P(xt∣h<t,c)和 P ( x ∣ h < t ) P(x | h_{<t}) P(x∣h<t)之间的距离,鼓励SVEN和原始LM产生的令牌级概率分布之间的相似性。
Overall Loss Function
L
=
L
L
M
+
w
C
T
⋅
L
C
T
+
w
K
L
⋅
L
K
L
L = L_{LM} + w_{CT} \cdot L_{CT} + w_{KL} \cdot L_{KL}
L=LLM+wCT⋅LCT+wKL⋅LKL
对比
SVEN相对于SafeCoder,其实是作用于code completion,额外加了KL Divergence来进行functiona correct的control。但实际上用于code generation的时候,KL的效果反而很差。
下图描述了如何将SVEN用于code generation和SafeCoder进行对比的细节
思考
在看完ICML’24之后,发现了一个问题,对于Standard Instruction Tuning,对于Code LLMs,用了Code Evol-Instruct这个数据集,对于general LLMs,用了LMSYS-Chat-1M数据集。疑惑的点在于,如果这两个数据集包含代码样本,怎么能够保证这些代码样本的安全性?于是去huggingface上看这两个数据集的情况,发现LMSYS-Chat-1M只涉及conversations,而Code Evol-Instruct则全是和代码相关的数据。后来觉得,可能是因为对于Code Evol-Instruct的Standard Instruction Tuning,并不涉及安全性的考量,只是用到了最普通的unmasked的negative log-likelihood loss进行fine-tune。对于安全性这一点,则是用security相关的数据集进行masked loss训练,所以standard instruction tuning的数据集中是否含有unsecure code并不影响。