Começando a partir de seu lançamento – muito recente 2.1, o TensorFlow suporta o que é chamado Treinamento de precisão mista (A seguir: MPT) para Keras. Neste submit, experimentamos o MPT e fornecemos alguns antecedentes. Declarado antecipadamente: em uma GPU da Tesla V100, nosso experimento baseado na CNN não revelou reduções substanciais no tempo de execução. Em um caso como esse, é difícil decidir se realmente escreve uma postagem ou não. Você poderia argumentar que, assim como na ciência, nulo Resultados são resultados. Ou, mais praticamente: eles abrem uma discussão que pode levar à descoberta de bugs, esclarecimento das instruções de uso e mais experimentações, entre outras.
Além disso, o tópico em si é interessante o suficiente para merecer algumas explicações de fundo – mesmo que os resultados não estejam bem lá ainda.
Então, para começar, vamos ouvir algum contexto no MPT.
Não se trata apenas de salvar a memória
Uma maneira de descrever o MPT em Tensorflow pode ser assim: MPT permite treinar modelos onde o pesos são do tipo float32
ou float64
como sempre (por razões de estabilidade numérica), mas o dados – Os tensores empurrados entre operações – têm menor precisão, a saber, 16 bits (float16
).
Esta frase provavelmente faria bem como um Tldr;
para o novo Página de documentação MPTtambém disponível para r no Tensorflow para r website. E com base nesta frase, você pode ser levado a pensar “Oh, claro, então é sobre salvar a memória”. Menos uso da memória implicaria então que você poderia executar tamanhos maiores de lote sem obter erros fora da memória.
É claro que isso está correto, e você verá isso acontecendo nos resultados da experimentação. Mas é apenas parte da história. A outra parte está relacionada à arquitetura da GPU e à computação paralela (não apenas na GPU paralela, como veremos).
AVX & CO.
As GPUs têm tudo a ver com paralelização. Mas também para as CPUs, os últimos dez anos viram desenvolvimentos importantes nos conjuntos de arquitetura e instruções. SIMD (instrução única de dados múltiplos) As operações executam uma instrução sobre um monte de dados de uma só vez. Por exemplo, dois operandos de 128 bits poderiam conter dois números inteiros de 64 bits cada, e estes podem ser adicionados emparelhados. Conceitualmente, isso lembra a adição de vetor em R (embora seja apenas um análogo!):
Ou, esses operando podem conter quatro números inteiros de 32 bits cada, nesse caso, poderíamos escrever simbolicamente
Com números inteiros de 16 bits, poderíamos novamente dobrar o número de elementos operados:
Na última década, as principais extensões de linguagem de montagem X-86 relacionadas ao SIMD foram AVX (Extensões vetoriais avançadas), AVX2, AVX-512 e FMA (mais na FMA em breve). Algum desses toques de um sino?
Your CPU helps directions that this TensorFlow binary was not compiled to make use of:
AVX2 FMA
Esta é uma linha que você provavelmente verá se está usando um binário de tensorflow pré-criado, em vez de compilar da fonte. (Mais tarde, ao relatar resultados de experimentação, também indicaremos os tempos de execução na CPU, para fornecer algum contexto para os tempos de execução da GPU em que estamos interessados-e apenas por diversão, também faremos um- muito Superficial – Comparação entre um binário de tensorflow instalado a partir da Pypi e uma que foi compilada manualmente.)
Embora todos esses AVXes sejam (basicamente) sobre uma extensão do processamento de vetores a tipos de dados cada vez maiores, a FMA é diferente e é uma coisa interessante de saber por si mesma – para quem faz processamento de sinais ou que use redes neurais.
FUSUSE MULTILY-ADD (FMA)
Fusado multiply-add é um tipo de multiplicar-se acumular operação. Em multiplicar-se acumularos operando são multiplicados e depois adicionados ao acumulador, acompanhando a soma em execução. Se “fundido”, toda a operação multiplique-then-add é realizada com um único arredondamento no remaining (em oposição ao arredondamento uma vez após a multiplicação e depois novamente após a adição). Geralmente, isso resulta em maior precisão.
Para as CPUs, a FMA foi introduzida simultaneamente com AVX2. A FMA pode ser realizada em escalares ou em vetores, “embalados” da maneira descrita no parágrafo anterior.
Por que dissemos que isso period tão interessante para os cientistas de dados? Bem, muitas operações – produtos DOT, multiplicações de matriz, convoluções – envolvem multiplicações seguidas de adições. “Multiplicação de matrizes” aqui realmente nos deixa deixando o reino das CPUs e pule para as GPUs, porque o que o MPT faz é usar o novo nvidia Núcleos tensores que estendem a FMA de escalares/vetores a matrizes.
Núcleos tensores
Como documentadoMPT requer GPUs com Capacidade de computação > = 7.0. As respectivas GPUs, além do regular Núcleos CUDAtêm os chamados “núcleos tensores” que executam FMA em matrizes:
A operação ocorre em matrizes 4×4; As multiplicações acontecem em operando de 16 bits, enquanto o resultado remaining pode ser de 16 bits ou 32 bits.
Podemos ver como isso é imediatamente relevante para as operações envolvidas na aprendizagem profunda; Os detalhes, no entanto, são não necessariamente claro.
Deixando esses internos para os especialistas, agora prosseguimos para o experimento actual.
Experimentos
Conjunto de dados
Com suas imagens do tamanho de 28x28px / 32x32px, nem o MNIST nem o CIFAR pareciam particularmente adequados para desafiar a GPU. Em vez disso, escolhemos Imagenetteo “pequeno imagenet” criado pelo quick.ai Pessoal, composto por 10 courses: Tench, Springer inglesa, cassete, serra de corrente, igreja, chifre francês, caminhão de lixo, bomba de gás, bola de golfe, e pára -quedas. Aqui estão alguns exemplos, retirados da versão de 320px:

Figura 3: Exemplos das 10 courses de imagenette.
Essas imagens foram redimensionadas – mantendo a proporção – de modo que a dimensão maior tenha comprimento 320px. Como parte do pré -processamento, redimensionaremos ainda mais para 256x256px, para trabalhar com uma boa potência de 2.
O conjunto de dados pode ser convenientemente obtido por meio de uso tfdsa interface R aos conjuntos de dados do TensorFlow.
library(keras)
# wants model 2.1
library(tensorflow)
library(tfdatasets)
# out there from github: devtools::install_github("rstudio/tfds")
library(tfds)
# to make use of TensorFlow Datasets, we want the Python backend
# usually, simply use tfds::install_tfds for this
# as of this writing although, we want a nightly construct of TensorFlow Datasets
# envname ought to seek advice from no matter surroundings you run TensorFlow in
reticulate::py_install("tfds-nightly", envname = "r-reticulate")
# on first execution, this downloads the dataset
imagenette <- tfds_load("imagenette/320px")
# extract prepare and take a look at elements
prepare <- imagenette$prepare
take a look at <- imagenette$validation
# batch measurement for the preliminary run
batch_size <- 32
# 12895 is the variety of objects within the coaching set
buffer_size <- 12895/batch_size
# coaching dataset is resized, scaled to between 0 and 1,
# cached, shuffled, and divided into batches
train_dataset <- prepare %>%
dataset_map(operate(report) {
report$picture <- report$picture %>%
tf$picture$resize(measurement = c(256L, 256L)) %>%
tf$truediv(255)
report
}) %>%
dataset_cache() %>%
dataset_shuffle(buffer_size) %>%
dataset_batch(batch_size) %>%
dataset_map(unname)
# take a look at dataset is resized, scaled to between 0 and 1, and divided into batches
test_dataset <- take a look at %>%
dataset_map(operate(report) {
report$picture <- report$picture %>%
tf$picture$resize(measurement = c(256L, 256L)) %>%
tf$truediv(255)
report}) %>%
dataset_batch(batch_size) %>%
dataset_map(unname)
No código acima, cache o conjunto de dados após as operações de redimensionamento e escala, pois queremos minimizar o tempo de pré -processamento gasto na CPU.
Configurando MPT
Nosso experimento usa keras match
– Em oposição a um loop de treinamento personalizado – e, dadas essas pré -condições, a execução do MPT é principalmente uma questão de adicionar três linhas de código. (Há uma pequena mudança no modelo, como veremos em um momento.)
Dizemos a Keras para usar o mixed_float16 Coverage
e verifique se os tensores têm tipo float16
enquanto o Variables
(pesos) ainda são do tipo float32
:
# when you learn this at a later time and get an error right here,
# try whether or not the placement within the codebase has modified
mixed_precision <- tf$keras$mixed_precision$experimental
coverage <- mixed_precision$Coverage('mixed_float16')
mixed_precision$set_policy(coverage)
# float16
coverage$compute_dtype
# float32
coverage$variable_dtype
O modelo é um convite direto, com o número de filtros sendo múltiplos de 8, conforme especificado no documentação. Há uma coisa a observar: por razões de estabilidade numérica, o tensor de saída actual do modelo deve ser do tipo float32
.
mannequin <- keras_model_sequential() %>%
layer_conv_2d(filters = 32, kernel_size = 5, strides = 2, padding = "identical", input_shape = c(256, 256, 3), activation = "relu") %>%
layer_batch_normalization() %>%
layer_conv_2d(filters = 64, kernel_size = 7, strides = 2, padding = "identical", activation = "relu") %>%
layer_batch_normalization() %>%
layer_conv_2d(filters = 128, kernel_size = 11, strides = 2, padding = "identical", activation = "relu") %>%
layer_batch_normalization() %>%
layer_global_average_pooling_2d() %>%
# separate logits from activations so precise outputs will be float32
layer_dense(models = 10) %>%
layer_activation("softmax", dtype = "float32")
mannequin %>% compile(
loss = "sparse_categorical_crossentropy",
optimizer = "adam",
metrics = "accuracy")
mannequin %>%
match(train_dataset, validation_data = test_dataset, epochs = 20)
Resultados
O experimento principal foi realizado em um Tesla V100 com 16g de memória. Apenas por curiosidade, executamos o mesmo modelo em quatro outras condições, nenhum dos quais cumpre o pré -requisito de ter um Capacidade de computação igual a pelo menos 7,0. Mencionaremos rapidamente esses após os principais resultados.
Com o modelo acima, a precisão remaining (remaining como em: após 20 épocas) flutuou cerca de 0,78:
Epoch 16/20
403/403 (==============================) - 12s 29ms/step - loss: 0.3365 -
accuracy: 0.8982 - val_loss: 0.7325 - val_accuracy: 0.8060
Epoch 17/20
403/403 (==============================) - 12s 29ms/step - loss: 0.3051 -
accuracy: 0.9084 - val_loss: 0.6683 - val_accuracy: 0.7820
Epoch 18/20
403/403 (==============================) - 11s 28ms/step - loss: 0.2693 -
accuracy: 0.9208 - val_loss: 0.8588 - val_accuracy: 0.7840
Epoch 19/20
403/403 (==============================) - 11s 28ms/step - loss: 0.2274 -
accuracy: 0.9358 - val_loss: 0.8692 - val_accuracy: 0.7700
Epoch 20/20
403/403 (==============================) - 11s 28ms/step - loss: 0.2082 -
accuracy: 0.9410 - val_loss: 0.8473 - val_accuracy: 0.7460
Os números relatados abaixo são milissegundos por etapa, etapa sendo um passe sobre um único lote. Assim, em geral, dobrando o tamanho do lote, esperaríamos que o tempo de execução também dobrasse.
Aqui estão os tempos de execução, retirados da época 20, para cinco tamanhos de lote diferentes, comparando o MPT com um padrão Coverage
isso usa float32
por todo. (Devemos acrescentar que, além da primeira época, os tempos de execução por etapa flutuaram por no máximo um milissegundo em todas as condições.)
32 | 28 | 30 |
64 | 52 | 56 |
128 | 97 | 106 |
256 | 188 | 206 |
512 | 377 | 415 |
Consistentemente, o MPT foi mais rápido, indicando que o caminho do código pretendido foi usado. Mas a aceleração não é tão grande.
Também assistimos à utilização da GPU durante as corridas. Estes variaram de cerca de 72% para batch_size
32 acima de ~ 78% para batch_size
128 para valores flutuantes, atingindo repetidamente 100%, para batch_size
512.
Como mencionado acima, apenas para ancorar esses valores, executamos o mesmo modelo em quatro outras condições, onde nenhuma aceleração period esperada. Embora esses tempos de execução não façam parte estritamente dos experimentos, nós os relatamos, caso o leitor esteja tão curioso sobre algum contexto quanto nós.
Em primeiro lugar, aqui está a tabela equivalente para um Titan XP com 12g de memória e Capacidade de computação 6.1.
32 | 44 | 38 |
64 | 70 | 70 |
128 | 142 | 136 |
256 | 270 | 270 |
512 | 518 | 539 |
Como esperado, não há superioridade consistente do MPT; Como um aparte, olhando para os valores em geral (especialmente em comparação com os tempos de execução da CPU!) Você pode concluir que, felizmente, nem sempre é necessário a GPU mais recente e mais recente para treinar redes neurais!
Em seguida, damos mais um passo pela escada de {hardware}. Aqui estão os tempos de execução de um quadro M2200 (4G, Capacidade de computação 5.2). (As três corridas que não têm um número travadas com fora da memória.)
32 | 186 | 197 |
64 | 352 | 375 |
128 | 687 | 746 |
256 | 1000 | – |
512 | – | – |
Desta vez, na verdade, vemos como o aspecto puro da memória da memória desempenha um papel: com o MPT, podemos executar lotes do tamanho 256; Sem, recebemos um erro fora da memória.
Agora, também comparamos com o tempo de execução na CPU (Intel Core i7, velocidade do relógio 2,9GHz). Para ser sincero, paramos depois de uma única época. Com um batch_size
de 32 e executando uma instalação pré -construída padrão do tensorflow, uma única etapa agora levou 321 – não milissegundos, mas segundos. Apenas por diversão, comparamos a um tensorflow manualmente construído que pode usar Avx2 e FMA Instruções (esse tópico pode de fato merecer um experimento dedicado): O tempo de execução por etapa foi reduzido para 304 segundos/etapa.
Conclusão
Resumindo, nosso experimento não mostrou reduções importantes nos tempos de execução – por razões ainda não claras. Ficaríamos felizes em incentivar uma discussão nos comentários!
Não obstante os resultados experimentais, esperamos que você tenha gostado de obter algumas informações básicas sobre um tópico não muito discutido. Obrigado pela leitura!