chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
---
|
||||
title: Switch Transformer
|
||||
summary: >
|
||||
This is an annotated implementation/tutorial a miniature version of Switch Transformer in PyTorch.
|
||||
---
|
||||
|
||||
# Switch Transformer
|
||||
|
||||
This is a miniature [PyTorch](https://pytorch.org) implementation of the paper
|
||||
[Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity](https://arxiv.org/abs/2101.03961).
|
||||
Our implementation only has a few million parameters and doesn't do model parallel distributed training.
|
||||
It does single GPU training, but we implement the concept of switching as described in the paper.
|
||||
|
||||
The Switch Transformer uses different parameters for each token by switching among parameters
|
||||
based on the token.
|
||||
Therefore, only a fraction of parameters are chosen for each token.
|
||||
So you can have more parameters but less computational cost.
|
||||
|
||||
The switching happens at the Position-wise Feedforward network (FFN) of each transformer block.
|
||||
Position-wise feedforward network consists of two sequentially fully connected layers.
|
||||
In switch transformer we have multiple FFNs (multiple experts),
|
||||
and we chose which one to use based on a router.
|
||||
The output is a set of probabilities for picking a FFN,
|
||||
and we pick the one with the highest probability and only evaluate that.
|
||||
So essentially the computational cost is the same as having a single FFN.
|
||||
In our implementation this doesn't parallelize well when you have many or large FFNs since it's all
|
||||
happening on a single GPU.
|
||||
In a distributed setup you would have each FFN (each very large) on a different device.
|
||||
|
||||
The paper introduces another loss term to balance load among the experts (FFNs) and
|
||||
discusses dropping tokens when routing is not balanced.
|
||||
|
||||
Here's [the training code](experiment.html) and a notebook for training a switch transformer on Tiny Shakespeare dataset.
|
||||
|
||||
[](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/transformers/switch/experiment.ipynb)
|
||||
"""
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from labml_nn.transformers.feed_forward import FeedForward
|
||||
from labml_nn.transformers.mha import MultiHeadAttention
|
||||
from labml_nn.utils import clone_module_list
|
||||
|
||||
|
||||
class SwitchFeedForward(nn.Module):
|
||||
"""
|
||||
## Routing among multiple FFNs
|
||||
"""
|
||||
|
||||
def __init__(self, *,
|
||||
capacity_factor: float,
|
||||
drop_tokens: bool,
|
||||
is_scale_prob: bool,
|
||||
n_experts: int,
|
||||
expert: FeedForward,
|
||||
d_model: int):
|
||||
"""
|
||||
* `capacity_factor` is the capacity of each expert as a factor relative to ideally balanced load
|
||||
* `drop_tokens` specifies whether to drop tokens if more tokens are routed to an expert than the capacity
|
||||
* `is_scale_prob` specifies whether to multiply the input to the FFN by the routing probability
|
||||
* `n_experts` is the number of experts
|
||||
* `expert` is the expert layer, a [FFN module](../feed_forward.html)
|
||||
* `d_model` is the number of features in a token embedding
|
||||
* `d_ff` is the number of features in the hidden layer of the FFN
|
||||
* `dropout` is dropout probability in the FFN
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.capacity_factor = capacity_factor
|
||||
self.is_scale_prob = is_scale_prob
|
||||
self.n_experts = n_experts
|
||||
self.drop_tokens = drop_tokens
|
||||
|
||||
# make copies of the FFNs
|
||||
self.experts = clone_module_list(expert, n_experts)
|
||||
# Routing layer and softmax
|
||||
self.switch = nn.Linear(d_model, n_experts)
|
||||
self.softmax = nn.Softmax(dim=-1)
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
"""
|
||||
* `x` is the input to the switching module with shape `[seq_len, batch_size, d_model]`
|
||||
"""
|
||||
|
||||
# Capture the shape to change shapes later
|
||||
seq_len, batch_size, d_model = x.shape
|
||||
# Flatten the sequence and batch dimensions
|
||||
x = x.view(-1, d_model)
|
||||
|
||||
# Get routing probabilities for each of the tokens.
|
||||
# $$p_i(x) = \frac{e^{h(x)_i}}{\sum^N_j e^{h(x)_j}}$$
|
||||
# where $N$ is the number of experts `n_experts` and
|
||||
# $h(\cdot)$ is the linear transformation of token embeddings.
|
||||
route_prob = self.softmax(self.switch(x))
|
||||
|
||||
# Get the maximum routing probabilities and the routes.
|
||||
# We route to the expert with highest probability
|
||||
route_prob_max, routes = torch.max(route_prob, dim=-1)
|
||||
|
||||
# Get indexes of tokens going to each expert
|
||||
indexes_list = [torch.eq(routes, i).nonzero(as_tuple=True)[0] for i in range(self.n_experts)]
|
||||
|
||||
# Initialize an empty tensor to store outputs
|
||||
final_output = x.new_zeros(x.shape)
|
||||
|
||||
# Capacity of each expert.
|
||||
# $$\mathrm{expert\;capacity} =
|
||||
# \frac{\mathrm{tokens\;per\;batch}}{\mathrm{number\;of\;experts}}
|
||||
# \times \mathrm{capacity\;factor}$$
|
||||
capacity = int(self.capacity_factor * len(x) / self.n_experts)
|
||||
# Number of tokens routed to each expert.
|
||||
counts = x.new_tensor([len(indexes_list[i]) for i in range(self.n_experts)])
|
||||
|
||||
# Initialize an empty list of dropped tokens
|
||||
dropped = []
|
||||
# Only drop tokens if `drop_tokens` is `True`.
|
||||
if self.drop_tokens:
|
||||
# Drop tokens in each of the experts
|
||||
for i in range(self.n_experts):
|
||||
# Ignore if the expert is not over capacity
|
||||
if len(indexes_list[i]) <= capacity:
|
||||
continue
|
||||
# Shuffle indexes before dropping
|
||||
indexes_list[i] = indexes_list[i][torch.randperm(len(indexes_list[i]))]
|
||||
# Collect the tokens over capacity as dropped tokens
|
||||
dropped.append(indexes_list[i][capacity:])
|
||||
# Keep only the tokens upto the capacity of the expert
|
||||
indexes_list[i] = indexes_list[i][:capacity]
|
||||
|
||||
# Get outputs of the expert FFNs
|
||||
expert_output = [self.experts[i](x[indexes_list[i], :]) for i in range(self.n_experts)]
|
||||
|
||||
# Assign to final output
|
||||
for i in range(self.n_experts):
|
||||
final_output[indexes_list[i], :] = expert_output[i]
|
||||
|
||||
# Pass through the dropped tokens
|
||||
if dropped:
|
||||
dropped = torch.cat(dropped)
|
||||
final_output[dropped, :] = x[dropped, :]
|
||||
|
||||
if self.is_scale_prob:
|
||||
# Multiply by the expert outputs by the probabilities $y = p_i(x) E_i(x)$
|
||||
final_output = final_output * route_prob_max.view(-1, 1)
|
||||
else:
|
||||
# Don't scale the values but multiply by $\frac{p}{\hat{p}} = 1$ so that the gradients flow
|
||||
# (this is something we experimented with).
|
||||
final_output = final_output * (route_prob_max / route_prob_max.detach()).view(-1, 1)
|
||||
|
||||
# Change the shape of the final output back to `[seq_len, batch_size, d_model]`
|
||||
final_output = final_output.view(seq_len, batch_size, d_model)
|
||||
|
||||
# Return
|
||||
#
|
||||
# * the final output
|
||||
# * number of tokens routed to each expert
|
||||
# * sum of probabilities for each expert
|
||||
# * number of tokens dropped.
|
||||
# * routing probabilities of the selected experts
|
||||
#
|
||||
# These are used for the load balancing loss and logging
|
||||
return final_output, counts, route_prob.sum(0), len(dropped), route_prob_max
|
||||
|
||||
|
||||
class SwitchTransformerLayer(nn.Module):
|
||||
"""
|
||||
# Switch Transformer Block
|
||||
|
||||
This is the same as [normal transformer block](../models.html#TransformerLayer)
|
||||
with handling extra outputs of switch feedforward module.
|
||||
"""
|
||||
|
||||
def __init__(self, *,
|
||||
d_model: int,
|
||||
attn: MultiHeadAttention,
|
||||
feed_forward: SwitchFeedForward,
|
||||
dropout_prob: float):
|
||||
"""
|
||||
* `d_model` is the token embedding size
|
||||
* `attn` is the attention module
|
||||
* `feed_forward` is the feed forward module (which is the switching module in this case)
|
||||
* `dropout_prob` is the probability of dropping out after self attention and FFN
|
||||
"""
|
||||
super().__init__()
|
||||
self.size = d_model
|
||||
self.attn = attn
|
||||
self.feed_forward = feed_forward
|
||||
self.dropout = nn.Dropout(dropout_prob)
|
||||
self.norm_self_attn = nn.LayerNorm([d_model])
|
||||
self.norm_ff = nn.LayerNorm([d_model])
|
||||
|
||||
def forward(self, *,
|
||||
x: torch.Tensor,
|
||||
mask: torch.Tensor):
|
||||
# Normalize the vectors before doing self attention
|
||||
z = self.norm_self_attn(x)
|
||||
# Run through self attention, i.e. keys and values are from self
|
||||
self_attn = self.attn(query=z, key=z, value=z, mask=mask)
|
||||
# Add the self attention results
|
||||
x = x + self.dropout(self_attn)
|
||||
|
||||
# Normalize for feed-forward
|
||||
z = self.norm_ff(x)
|
||||
# Pass through the switching feed-forward network
|
||||
ff, counts, route_prob, n_dropped, route_prob_max = self.feed_forward(z)
|
||||
# Add the feed-forward results back
|
||||
x = x + self.dropout(ff)
|
||||
|
||||
return x, counts, route_prob, n_dropped, route_prob_max
|
||||
|
||||
|
||||
class SwitchTransformer(nn.Module):
|
||||
"""
|
||||
## Switch Transformer
|
||||
"""
|
||||
|
||||
def __init__(self, layer: SwitchTransformerLayer, n_layers: int):
|
||||
super().__init__()
|
||||
# Make copies of the transformer layer
|
||||
self.layers = clone_module_list(layer, n_layers)
|
||||
# Final normalization layer
|
||||
self.norm = nn.LayerNorm([layer.size])
|
||||
|
||||
def forward(self, x: torch.Tensor, mask: torch.Tensor):
|
||||
# Run through each transformer layer
|
||||
counts, route_prob, n_dropped, route_prob_max = [], [], [], []
|
||||
for layer in self.layers:
|
||||
x, f, p, n_d, p_max = layer(x=x, mask=mask)
|
||||
counts.append(f)
|
||||
route_prob.append(p)
|
||||
n_dropped.append(n_d)
|
||||
route_prob_max.append(p_max)
|
||||
# Finally, normalize the vectors
|
||||
x = self.norm(x)
|
||||
#
|
||||
return x, torch.stack(counts), torch.stack(route_prob), n_dropped, torch.stack(route_prob_max)
|
||||
@@ -0,0 +1,228 @@
|
||||
{
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "Switch Transformer",
|
||||
"provenance": [],
|
||||
"collapsed_sections": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"accelerator": "GPU"
|
||||
},
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "AYV_dMVDxyc2"
|
||||
},
|
||||
"source": [
|
||||
"[](https://github.com/labmlai/annotated_deep_learning_paper_implementations)\n",
|
||||
"[](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/transformers/switch/experiment.ipynb) \n",
|
||||
"\n",
|
||||
"## Switch Transformer\n",
|
||||
"\n",
|
||||
"This is an experiment training Shakespeare dataset with a small Switch Transformer."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "AahG_i2y5tY9"
|
||||
},
|
||||
"source": [
|
||||
"Install the `labml-nn` package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "ZCzmCrAIVg0L",
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"outputId": "41bb262e-d7e4-4dd9-cf8c-b2a1724889b7"
|
||||
},
|
||||
"source": [
|
||||
"!pip install labml-nn"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "SE2VUQ6L5zxI"
|
||||
},
|
||||
"source": [
|
||||
"Imports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "0hJXx_g0wS2C"
|
||||
},
|
||||
"source": [
|
||||
"from labml import experiment\n",
|
||||
"from labml_nn.transformers.switch.experiment import Configs"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Lpggo0wM6qb-"
|
||||
},
|
||||
"source": [
|
||||
"Create an experiment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "bFcr9k-l4cAg"
|
||||
},
|
||||
"source": [
|
||||
"experiment.create(name=\"switch_transformer\")"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-OnHLi626tJt"
|
||||
},
|
||||
"source": [
|
||||
"Initialize configurations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "Piz0c5f44hRo"
|
||||
},
|
||||
"source": [
|
||||
"conf = Configs()"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "wwMzCqpD6vkL"
|
||||
},
|
||||
"source": [
|
||||
"Set experiment configurations and assign a configurations dictionary to override configurations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/",
|
||||
"height": 17
|
||||
},
|
||||
"id": "e6hmQhTw4nks",
|
||||
"outputId": "0bc4e738-adc7-4003-a030-4080df882bbb"
|
||||
},
|
||||
"source": [
|
||||
"experiment.configs(conf,\n",
|
||||
" # A dictionary of configurations to override\n",
|
||||
" {'tokenizer': 'character',\n",
|
||||
" 'text': 'tiny_shakespeare',\n",
|
||||
" 'optimizer.learning_rate': 1.,\n",
|
||||
" 'optimizer.optimizer': 'Noam',\n",
|
||||
" 'prompt': 'It is',\n",
|
||||
" 'prompt_separator': '',\n",
|
||||
"\n",
|
||||
" 'transformer': 'switch_transformer',\n",
|
||||
" 'is_scale_prob': False,\n",
|
||||
" 'n_experts': 4,\n",
|
||||
"\n",
|
||||
" 'drop_tokens': True,\n",
|
||||
" 'capacity_factor': 1.2,\n",
|
||||
"\n",
|
||||
" 'train_loader': 'shuffled_train_loader',\n",
|
||||
" 'valid_loader': 'shuffled_valid_loader',\n",
|
||||
"\n",
|
||||
" 'seq_len': 64,\n",
|
||||
" 'epochs': 128,\n",
|
||||
" 'batch_size': 32,\n",
|
||||
" 'inner_iterations': 25,\n",
|
||||
" })"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "EvI7MtgJ61w5"
|
||||
},
|
||||
"source": [
|
||||
"Set PyTorch models for loading and saving"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/",
|
||||
"height": 272
|
||||
},
|
||||
"id": "GDlt7dp-5ALt",
|
||||
"outputId": "93e0f3b1-d0fe-4525-d9f6-9ffab9ea7f9b"
|
||||
},
|
||||
"source": [
|
||||
"experiment.add_pytorch_models({'model': conf.model})"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KJZRf8527GxL"
|
||||
},
|
||||
"source": [
|
||||
"Start the experiment and run the training loop."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/",
|
||||
"height": 1000
|
||||
},
|
||||
"id": "aIAWo7Fw5DR8",
|
||||
"outputId": "12a92c2e-d248-436b-a6f1-7cf92b5289e9"
|
||||
},
|
||||
"source": [
|
||||
"# Start the experiment\n",
|
||||
"with experiment.start():\n",
|
||||
" conf.run()"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "oBXXlP2b7XZO"
|
||||
},
|
||||
"source": [
|
||||
""
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
---
|
||||
title: Switch Transformer Experiment
|
||||
summary: This experiment trains a small switch transformer on tiny Shakespeare dataset.
|
||||
---
|
||||
|
||||
# Switch Transformer Experiment
|
||||
|
||||
This is an annotated PyTorch experiment to train a switch transformer.
|
||||
|
||||
[](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/transformers/switch/experiment.ipynb)
|
||||
"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from labml import experiment, tracker
|
||||
from labml.configs import option
|
||||
from labml_nn.helpers.trainer import BatchIndex
|
||||
from labml_nn.experiments.nlp_autoregression import NLPAutoRegressionConfigs
|
||||
|
||||
|
||||
class AutoregressiveModel(nn.Module):
|
||||
"""
|
||||
## Auto regressive model
|
||||
"""
|
||||
|
||||
def __init__(self, n_vocab: int, d_model: int, transformer: nn.Module):
|
||||
super().__init__()
|
||||
# Token embedding module
|
||||
self.src_embed = nn.Embedding(n_vocab, d_model)
|
||||
# Transformer
|
||||
self.transformer = transformer
|
||||
# Final layer
|
||||
self.generator = nn.Linear(d_model, n_vocab)
|
||||
self.mask = None
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
# Initialize the subsequent mask
|
||||
if self.mask is None or self.mask.size(0) != len(x):
|
||||
from labml_nn.transformers.utils import subsequent_mask
|
||||
self.mask = subsequent_mask(len(x)).to(x.device)
|
||||
# Token embeddings
|
||||
x = self.src_embed(x)
|
||||
# Run it through the transformer
|
||||
res, counts, route_prob, n_dropped, route_prob_max = self.transformer(x, self.mask)
|
||||
# Generate logits of the next token
|
||||
res = self.generator(res)
|
||||
#
|
||||
return res, counts, route_prob, n_dropped, route_prob_max
|
||||
|
||||
|
||||
class Configs(NLPAutoRegressionConfigs):
|
||||
"""
|
||||
## Configurations
|
||||
|
||||
This extends [`NLPAutoRegressionConfigs`](../../experiments/nlp_autoregression.html).
|
||||
|
||||
The default configs can and will be over-ridden when we start the experiment
|
||||
"""
|
||||
|
||||
model: AutoregressiveModel
|
||||
transformer: nn.Module
|
||||
|
||||
# Token embedding size
|
||||
d_model: int = 128
|
||||
# Number of attention heads
|
||||
heads: int = 4
|
||||
# Dropout probability
|
||||
dropout: float = 0.0
|
||||
# Number of features in FFN hidden layer
|
||||
d_ff: int = 256
|
||||
# Number of transformer layers
|
||||
n_layers: int = 6
|
||||
# Number of experts
|
||||
n_experts: int = 4
|
||||
# Load balancing coefficient
|
||||
load_balancing_loss_ceof = 0.01
|
||||
# Whether to scale the chosen expert outputs by the routing probability
|
||||
is_scale_prob: bool = True
|
||||
# Whether to drop tokens
|
||||
drop_tokens: bool = False
|
||||
# Capacity factor to determine capacity of each model
|
||||
capacity_factor: float = 1.0
|
||||
|
||||
def init(self):
|
||||
super().init()
|
||||
# Initialize tracking indicators
|
||||
tracker.set_scalar("lb_loss.*", False)
|
||||
tracker.set_scalar("route.*", False)
|
||||
tracker.set_scalar("dropped.*", False)
|
||||
|
||||
def step(self, batch: any, batch_idx: BatchIndex):
|
||||
"""
|
||||
### Training or validation step
|
||||
"""
|
||||
|
||||
# Move data to the device
|
||||
data, target = batch[0].to(self.device), batch[1].to(self.device)
|
||||
|
||||
# Update global step (number of tokens processed) when in training mode
|
||||
if self.mode.is_train:
|
||||
tracker.add_global_step(data.shape[0] * data.shape[1])
|
||||
|
||||
# Get model outputs.
|
||||
output, counts, route_prob, n_dropped, route_prob_max = self.model(data)
|
||||
|
||||
# Calculate and cross entropy loss
|
||||
cross_entropy_loss = self.loss_func(output, target)
|
||||
# Total number of tokens processed, $T$, in the current batch $\mathscr{B}$
|
||||
total = counts.sum(dim=-1, keepdims=True)
|
||||
# Fraction of tokens routed to each expert
|
||||
# $$f_i = \frac{1}{T} \sum_{x \in \mathscr{B}} \mathbf{1} \{ \mathop{argmax} p(x), i \}$$
|
||||
# $f_i$ is the count of tokens where the argmax of $p(x)$ is equal to $i$.
|
||||
route_frac = counts / total
|
||||
# Mean routing probability
|
||||
# $$P_i = \frac{1}{T} \sum_{x \in \mathscr{B}} p_i (x)$$
|
||||
route_prob = route_prob / total
|
||||
# Load balancing loss
|
||||
# $$\mathscr{L} = N \sum_{i=1}^N f_i \cdot P_i$$
|
||||
# $\mathscr{L}$ is the loss for a single layer and here we are
|
||||
# taking the sum of losses across all layers.
|
||||
load_balancing_loss = self.n_experts * (route_frac * route_prob).sum()
|
||||
|
||||
# Track stats
|
||||
tracker.add('dropped.', total.new_tensor(n_dropped) / total)
|
||||
tracker.add('route.min.', route_frac.min())
|
||||
tracker.add('route.max.', route_frac.max())
|
||||
tracker.add('route.std.', route_frac.std())
|
||||
tracker.add('route.max_prob.', route_prob_max)
|
||||
tracker.add("loss.", cross_entropy_loss)
|
||||
tracker.add("lb_loss.", load_balancing_loss)
|
||||
|
||||
# Combined loss.
|
||||
# The load balancing loss is multiplied by a coefficient $\alpha$ which is
|
||||
# set to something small like $\alpha = 0.01$.
|
||||
loss = cross_entropy_loss + self.load_balancing_loss_ceof * load_balancing_loss
|
||||
|
||||
# Calculate and log accuracy
|
||||
self.accuracy(output, target)
|
||||
self.accuracy.track()
|
||||
|
||||
# Train the model
|
||||
if self.mode.is_train:
|
||||
# Calculate gradients
|
||||
loss.backward()
|
||||
# Clip gradients
|
||||
torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=self.grad_norm_clip)
|
||||
# Take optimizer step
|
||||
self.optimizer.step()
|
||||
# Log the model parameters and gradients on last batch of every epoch
|
||||
if batch_idx.is_last:
|
||||
tracker.add('model', self.model)
|
||||
# Clear the gradients
|
||||
self.optimizer.zero_grad()
|
||||
|
||||
# Save the tracked metrics
|
||||
tracker.save()
|
||||
|
||||
|
||||
@option(Configs.model)
|
||||
def autoregressive_model(c: Configs):
|
||||
"""
|
||||
### Initialize the auto-regressive model
|
||||
"""
|
||||
m = AutoregressiveModel(c.n_tokens, c.d_model, c.transformer)
|
||||
return m.to(c.device)
|
||||
|
||||
|
||||
@option(Configs.transformer)
|
||||
def switch_transformer(c: Configs):
|
||||
"""
|
||||
### Initialize the switch transformer
|
||||
"""
|
||||
from labml_nn.transformers.switch import SwitchTransformer, SwitchTransformerLayer, SwitchFeedForward
|
||||
from labml_nn.transformers import MultiHeadAttention
|
||||
from labml_nn.transformers.feed_forward import FeedForward
|
||||
|
||||
return SwitchTransformer(
|
||||
SwitchTransformerLayer(d_model=c.d_model,
|
||||
attn=MultiHeadAttention(c.heads, c.d_model, c.dropout),
|
||||
feed_forward=SwitchFeedForward(capacity_factor=c.capacity_factor,
|
||||
drop_tokens=c.drop_tokens,
|
||||
is_scale_prob=c.is_scale_prob,
|
||||
n_experts=c.n_experts,
|
||||
expert=FeedForward(c.d_model, c.d_ff, c.dropout),
|
||||
d_model=c.d_model),
|
||||
dropout_prob=c.dropout),
|
||||
c.n_layers)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
### Run the experiment
|
||||
"""
|
||||
# Create experiment
|
||||
experiment.create(name="switch_transformer", comment='')
|
||||
# Create configs
|
||||
conf = Configs()
|
||||
# Load configurations
|
||||
experiment.configs(conf,
|
||||
# A dictionary of configurations to override
|
||||
{'tokenizer': 'character',
|
||||
'text': 'tiny_shakespeare',
|
||||
'optimizer.learning_rate': 1.,
|
||||
'optimizer.optimizer': 'Noam',
|
||||
'prompt': 'It is',
|
||||
'prompt_separator': '',
|
||||
|
||||
'transformer': 'switch_transformer',
|
||||
'n_experts': 4,
|
||||
|
||||
'drop_tokens': True,
|
||||
'capacity_factor': 1.2,
|
||||
|
||||
'train_loader': 'shuffled_train_loader',
|
||||
'valid_loader': 'shuffled_valid_loader',
|
||||
|
||||
'seq_len': 64,
|
||||
'epochs': 128,
|
||||
'batch_size': 32,
|
||||
'inner_iterations': 25,
|
||||
})
|
||||
|
||||
# Set models for saving and loading
|
||||
experiment.add_pytorch_models({'model': conf.model})
|
||||
|
||||
# Start the experiment
|
||||
with experiment.start():
|
||||
# `TrainValidConfigs.run`
|
||||
conf.run()
|
||||
|
||||
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
# [Switch Transformer](https://nn.labml.ai/transformers/switch/index.html)
|
||||
|
||||
This is a miniature [PyTorch](https://pytorch.org) implementation of the paper
|
||||
[Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity](https://arxiv.org/abs/2101.03961).
|
||||
Our implementation only has a few million parameters and doesn't do model parallel distributed training.
|
||||
It does single GPU training, but we implement the concept of switching as described in the paper.
|
||||
|
||||
The Switch Transformer uses different parameters for each token by switching among parameters
|
||||
based on the token.
|
||||
Therefore, only a fraction of parameters are chosen for each token.
|
||||
So you can have more parameters but less computational cost.
|
||||
|
||||
The switching happens at the Position-wise Feedforward network (FFN) of each transformer block.
|
||||
Position-wise feedforward network consists of two sequentially fully connected layers.
|
||||
In switch transformer we have multiple FFNs (multiple experts),
|
||||
and we chose which one to use based on a router.
|
||||
The output is a set of probabilities for picking a FFN,
|
||||
and we pick the one with the highest probability and only evaluate that.
|
||||
So essentially the computational cost is the same as having a single FFN.
|
||||
In our implementation this doesn't parallelize well when you have many or large FFNs since it's all
|
||||
happening on a single GPU.
|
||||
In a distributed setup you would have each FFN (each very large) on a different device.
|
||||
|
||||
The paper introduces another loss term to balance load among the experts (FFNs) and
|
||||
discusses dropping tokens when routing is not balanced.
|
||||
|
||||
Here's [the training code](experiment.html) and a notebook for training a switch transformer on Tiny Shakespeare dataset.
|
||||
Reference in New Issue
Block a user