Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

327 lines
12 KiB
Python

# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import numpy as np
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
__all__ = [
"prepare_qkv_ofa",
"mha_ofa_forward",
"encoder_ofa_forward",
"encoder_layer_ofa_forward",
"compute_neuron_head_importance",
"reorder_neuron_head",
]
def prepare_qkv_ofa(self, query, key, value, cache=None):
q = self.q_proj(query)
if hasattr(self.q_proj, "fn") and self.q_proj.fn.cur_config["expand_ratio"] is not None:
self.num_heads = int(self.num_heads * self.q_proj.fn.cur_config["expand_ratio"])
q = paddle.reshape(x=q, shape=[0, 0, self.num_heads, self.head_dim])
q = paddle.transpose(x=q, perm=[0, 2, 1, 3])
if isinstance(cache, self.StaticCache):
# for encoder-decoder attention in inference and has cached
k, v = cache.k, cache.v
else:
k, v = self.compute_kv(key, value)
if isinstance(cache, self.Cache):
# for decoder self-attention in inference
k = paddle.concat([cache.k, k], axis=2)
v = paddle.concat([cache.v, v], axis=2)
cache = self.Cache(k, v)
return (q, k, v) if cache is None else (q, k, v, cache)
def mha_ofa_forward(self, query, key, value, attn_mask=None, cache=None):
"""
monkey patch for MultiHeadAttention forward to accept head_mask
attn_mask[0] = attn_mask, attn_mask[1] = head_mask
"""
key = query if key is None else key
value = query if value is None else value
# compute q ,k ,v
if cache is None:
q, k, v = self._prepare_qkv(query, key, value, cache)
else:
q, k, v, cache = self._prepare_qkv(query, key, value, cache)
# scale dot product attention
product = paddle.matmul(x=q * (self.head_dim**-0.5), y=k, transpose_y=True)
if attn_mask[0] is not None:
# TODO(guosheng): support bool mask
product = product + attn_mask[0]
weights = F.softmax(product)
if self.dropout:
weights = F.dropout(weights, self.dropout, training=self.training, mode="upscale_in_train")
if attn_mask[1] is not None:
weights = weights * attn_mask[1]
out = paddle.matmul(weights, v)
# combine heads
out = paddle.transpose(out, perm=[0, 2, 1, 3])
out = paddle.reshape(x=out, shape=[0, 0, out.shape[2] * out.shape[3]])
# project to output
out = self.out_proj(out)
outs = [out]
if self.need_weights:
outs.append(weights)
if cache is not None:
outs.append(cache)
if hasattr(self.q_proj, "fn") and self.q_proj.fn.cur_config["expand_ratio"] is not None:
self.num_heads = int(float(self.num_heads) / self.q_proj.fn.cur_config["expand_ratio"])
return out if len(outs) == 1 else tuple(outs)
def encoder_ofa_forward(
self,
src,
src_mask=[None, None],
cache=None,
output_attentions=False,
output_hidden_states=False,
return_dict=False,
):
"""
monkey patch for TransformerEncoder forward to accept head_mask
attn_mask[0] = attn_mask, attn_mask[1] = head_mask
"""
output = src
if src_mask[1] is not None:
head_mask = src_mask[1]
if len(head_mask.shape) == 1:
head_mask = paddle.unsqueeze(paddle.unsqueeze(paddle.unsqueeze(paddle.unsqueeze(head_mask, 0), 0), -1), -1)
head_mask = paddle.expand(head_mask, shape=[self.num_layers] + head_mask.shape[1:])
elif len(head_mask.shape) == 2:
head_mask = paddle.unsqueeze(paddle.unsqueeze(paddle.unsqueeze(head_mask, 1), -1), -1)
else:
head_mask = [None] * self.num_layers
for i, mod in enumerate(self.layers):
output = mod(output, src_mask=[src_mask[0], head_mask[i]])
if self.norm is not None:
output = self.norm(output)
return output
def encoder_layer_ofa_forward(self, src, src_mask=None, cache=None, output_attentions=False):
residual = src
if self.normalize_before:
src = self.norm1(src)
# Add cache for encoder for the usage like UniLM
if cache is None:
src = self.self_attn(src, src, src, src_mask)
else:
src, incremental_cache = self.self_attn(src, src, src, src_mask, cache)
src = residual + self.dropout1(src)
if not self.normalize_before:
src = self.norm1(src)
residual = src
if self.normalize_before:
src = self.norm2(src)
src = self.linear2(self.dropout(self.activation(self.linear1(src))))
src = residual + self.dropout2(src)
if not self.normalize_before:
src = self.norm2(src)
return src if cache is None else (src, incremental_cache)
def reorder_head(layer, index):
"""
Reorder head weights according index.
Args:
layer(paddle.nn.Layer): the instance of `paddle.nn.MultiHeadAttention` layer.
index(list): the sort indices of multi-head.
"""
assert isinstance(
layer, nn.MultiHeadAttention
), "layer in reorder_head must be the instance of `paddle.nn.MultiHeadAttention`."
n, a = layer.num_heads, layer.head_dim
idx = paddle.reshape(
paddle.index_select(paddle.reshape(paddle.arange(0, n * a, dtype="int64"), shape=[n, a]), index=index, axis=0),
shape=[-1],
)
def reorder_head_matrix(linearLayer, index, dim=1):
W = paddle.index_select(linearLayer.weight, index, axis=dim).detach()
if linearLayer.bias is not None:
if dim == 0:
b = paddle.assign(linearLayer.bias).detach()
else:
b = paddle.assign(paddle.index_select(linearLayer.bias, index, axis=0)).detach()
linearLayer.weight.stop_gradient = True
linearLayer.weight.set_value(W)
linearLayer.weight.stop_gradient = False
if linearLayer.bias is not None:
linearLayer.bias.stop_gradient = True
linearLayer.bias.set_value(b)
linearLayer.bias.stop_gradient = False
reorder_head_matrix(layer.q_proj.fn if hasattr(layer.q_proj, "fn") else layer.q_proj, idx)
reorder_head_matrix(layer.k_proj.fn if hasattr(layer.k_proj, "fn") else layer.k_proj, idx)
reorder_head_matrix(layer.v_proj.fn if hasattr(layer.v_proj, "fn") else layer.v_proj, idx)
reorder_head_matrix(layer.out_proj.fn if hasattr(layer.out_proj, "fn") else layer.out_proj, idx, dim=0)
def reorder_neuron(layer, index, dim=0):
"""
Reorder feed-forward weights according index.
Args:
layer(paddle.nn.Layer): the instance of `paddle.nn.Linear` layer.
index(list): the sort indices of feed-forward.
dim(int): select weights according to the dim.
"""
linearLayer = layer.fn if hasattr(layer, "fn") else layer
W = paddle.index_select(linearLayer.weight, index, axis=dim).detach()
if linearLayer.bias is not None:
if dim == 0:
b = paddle.assign(linearLayer.bias).detach()
else:
b = paddle.assign(paddle.index_select(linearLayer.bias, index, axis=0)).detach()
linearLayer.weight.stop_gradient = True
linearLayer.weight.set_value(W)
linearLayer.weight.stop_gradient = False
if linearLayer.bias is not None:
linearLayer.bias.stop_gradient = True
linearLayer.bias.set_value(b)
linearLayer.bias.stop_gradient = False
def reorder_neuron_head(model, head_importance, neuron_importance):
"""
Reorders weights according head importance and neuron importance
"""
# Reorders heads and ffn neurons
for layer, current_importance in enumerate(neuron_importance):
# Reorders heads
idx = paddle.argsort(head_importance[layer], descending=True)
reorder_head(model.base_model.encoder.layers[layer].self_attn, idx)
# Reorders neurons
idx = paddle.argsort(paddle.to_tensor(current_importance), descending=True)
reorder_neuron(model.base_model.encoder.layers[layer].linear1.fn, idx, dim=1)
reorder_neuron(model.base_model.encoder.layers[layer].linear2.fn, idx, dim=0)
def compute_neuron_head_importance(
model,
data_loader,
num_layers,
num_heads,
loss_fct=nn.loss.CrossEntropyLoss(),
intermediate_name="linear1",
output_name="linear2",
label_names=None,
):
"""
Computes the importance of multi-head attention and feed-forward neuron in
each transformer layer.
Args:
model(paddle.nn.Layer):
The instance of transformer model.
data_loader (DataLoader):
An iterable data loader is used for evaluate. An instance of
`paddle.io.Dataloader`.
num_layers (int):
Number of transformer layers.
num_heads (int):
Number of heads in each multi-head attention.
loss_fct (Loss|optional):
Loss function can be a `paddle.nn.Layer` instance. Default: `nn.loss.CrossEntropyLoss()`.
intermediate_name (str|optional):
The name of intermediate `Linear` layer in feed-forward.
Defaults to `linear1`.
output_name (str|optional):
The name of output `Linear` layer in feed-forward.
Defaults to `linear2`.
"""
head_importance = paddle.zeros(shape=[num_layers, num_heads], dtype="float32")
head_mask = paddle.ones(shape=[num_layers, num_heads], dtype="float32")
head_mask.stop_gradient = False
intermediate_weight = []
intermediate_bias = []
output_weight = []
for name, w in model.named_parameters():
if intermediate_name in name:
if len(w.shape) > 1:
intermediate_weight.append(w)
else:
intermediate_bias.append(w)
if output_name in name:
if len(w.shape) > 1:
output_weight.append(w)
neuron_importance = []
for w in intermediate_weight:
neuron_importance.append(np.zeros(shape=[w.shape[1]], dtype="float32"))
for i, batch in enumerate(data_loader):
labels = None
if isinstance(batch, list):
input_ids, segment_ids, labels = batch
logits = model(input_ids, segment_ids, attention_mask=[None, head_mask])
else:
if label_names is not None:
labels = []
for label in label_names:
labels.append(batch.pop(label))
labels = tuple(labels)
elif "labels" in batch:
labels = batch.pop("labels")
# For token cls tasks
for key in ("length", "seq_len"):
if key in batch:
batch.pop(key)
elif "start_positions" in batch and "end_positions" in batch:
labels = (batch.pop("start_positions"), batch.pop("end_positions"))
batch["attention_mask"] = [None, head_mask]
logits = model(**batch)
if loss_fct is not None:
loss = loss_fct(logits, labels)
else:
raise NotImplementedError(
"Model to be compressed is an instance of a custom class, "
"so function `loss_fct(logits, labels)` should "
"be implemented, and it should return a single float for precision "
"value, such as acc."
)
loss.backward()
head_importance += paddle.abs(paddle.to_tensor(head_mask.gradient()))
for w1, b1, w2, current_importance in zip(
intermediate_weight, intermediate_bias, output_weight, neuron_importance
):
current_importance += np.abs((np.sum(w1.numpy() * w1.gradient(), axis=0) + b1.numpy() * b1.gradient()))
current_importance += np.abs(np.sum(w2.numpy() * w2.gradient(), axis=1))
return head_importance, neuron_importance