Técnicas avançadas para melhorar o rendimento do LLM

Publicados: 2024-04-02
Mostrar índice
Desafios para alcançar maior rendimento para LLMs
Requisito de memória
Auto-regressividade e operação vinculada à memória
Soluções inovadoras para superar desafios de produtividade
Lotes contínuos
Atenção paginada
Atenção instantânea
Principais recursos do Flash Atenção:
Estudo de caso – Otimizando inferência com decodificação especulativa
Conclusão

No mundo acelerado da tecnologia, os Large Language Model (LLMs) tornaram-se atores-chave na forma como interagimos com a informação digital. Essas ferramentas poderosas podem escrever artigos, responder perguntas e até mesmo manter conversas, mas têm seus desafios. À medida que exigimos mais destes modelos, deparamo-nos com obstáculos, especialmente quando se trata de fazê-los funcionar de forma mais rápida e eficiente. Este blog é sobre como enfrentar esses obstáculos de frente.

Estamos mergulhando em algumas estratégias inteligentes projetadas para aumentar a velocidade de operação desses modelos, sem perder a qualidade de seus resultados. Imagine tentar melhorar a velocidade de um carro de corrida e, ao mesmo tempo, garantir que ele ainda possa navegar perfeitamente em curvas fechadas – é isso que buscamos com modelos de linguagem grande. Analisaremos métodos como Continuous Batching, que ajuda a processar informações com mais facilidade, e abordagens inovadoras, como Paged e Flash Attention, que tornam os LLMs mais atentos e mais rápidos em seu raciocínio digital.

Então, se você está curioso para ultrapassar os limites do que esses gigantes da IA ​​​​podem fazer, você está no lugar certo. Vamos explorar juntos como essas técnicas avançadas estão moldando o futuro dos LLMs, tornando-os mais rápidos e melhores do que nunca.

Desafios para alcançar maior rendimento para LLMs

Alcançar um rendimento mais elevado em Large Language Models (LLMs) enfrenta vários desafios significativos, cada um deles atuando como um obstáculo à velocidade e eficiência com que esses modelos podem operar. Um obstáculo principal é o grande requisito de memória necessário para processar e armazenar as grandes quantidades de dados com os quais esses modelos trabalham. À medida que os LLMs crescem em complexidade e tamanho, a demanda por recursos computacionais se intensifica, tornando difícil manter, e muito menos melhorar, as velocidades de processamento.

Outro grande desafio é a natureza auto-regressiva dos LLMs, especialmente em modelos utilizados para geração de texto. Isso significa que a saída de cada etapa depende das anteriores, criando um requisito de processamento sequencial que limita inerentemente a rapidez com que as tarefas podem ser executadas. Essa dependência sequencial geralmente resulta em um gargalo, pois cada etapa deve aguardar a conclusão de sua antecessora antes de poder prosseguir, dificultando os esforços para alcançar um rendimento mais alto.

Além disso, o equilíbrio entre precisão e velocidade é delicado. Melhorar o rendimento sem comprometer a qualidade do resultado é uma caminhada na corda bamba, exigindo soluções inovadoras que possam navegar no cenário complexo da eficiência computacional e da eficácia do modelo.

Esses desafios formam o pano de fundo contra o qual são feitos avanços na otimização do LLM, ampliando os limites do que é possível no domínio do processamento de linguagem natural e além.

Requisito de memória

A fase de decodificação gera um único token em cada intervalo de tempo, mas cada token depende dos tensores de chave e valor de todos os tokens anteriores (incluindo os tensores KV dos tokens de entrada calculados no pré-preenchimento e quaisquer novos tensores KV calculados até o intervalo de tempo atual) .

Portanto, para minimizar sempre os cálculos redundantes e evitar o recálculo de todos esses tensores para todos os tokens em cada intervalo de tempo, é possível armazená-los em cache na memória da GPU. A cada iteração, quando novos elementos são computados, eles são simplesmente adicionados ao cache em execução para serem usados ​​na próxima iteração. Isso é essencialmente conhecido como cache KV.

Isso reduz bastante a computação necessária, mas introduz um requisito de memória junto com requisitos de memória já mais altos para modelos de linguagem grandes, o que torna difícil a execução em GPUs comuns. Com o aumento do tamanho dos parâmetros do modelo (7B a 33B) e a maior precisão (fp16 a fp32), os requisitos de memória também aumentam. Vamos ver um exemplo da capacidade de memória necessária,

Como sabemos, os dois principais ocupantes da memória são

  1. Pesos próprios do modelo na memória, isso vem sem. de parâmetros como 7B e tipo de dados de cada parâmetro, por exemplo, 7B em fp16 (2 bytes) ~ = 14 GB de memória
  2. Cache KV: O cache usado para o valor-chave do estágio de autoatenção para evitar cálculos redundantes.

Tamanho do cache KV por token em bytes = 2 * (num_layers) * (hidden_size) * Precision_in_bytes

O primeiro fator 2 é responsável pelas matrizes K e V. Esses hidden_size e dim_head podem ser obtidos no cartão do modelo ou no arquivo de configuração.

A fórmula acima é por token, portanto, para uma sequência de entrada, será seq_len * size_of_kv_per_token. Portanto, a fórmula acima será transformada em,

Tamanho total do cache KV em bytes = (sequence_length) * 2 * (num_layers) * (hidden_size) * Precision_in_bytes

Por exemplo, com LLAMA 2 em fp16, o tamanho será (4096) * 2 * (32) * (4096) * 2, que é aproximadamente 2 GB.

O acima é para uma única entrada, com múltiplas entradas isso cresce rapidamente, essa alocação e gerenciamento de memória em trânsito torna-se, portanto, uma etapa crucial para alcançar o desempenho ideal, se não resultar em problemas de falta de memória e fragmentação.

Às vezes, o requisito de memória é maior do que a capacidade de nossa GPU; nesses casos, precisamos olhar para o paralelismo de modelo, paralelismo de tensor que não é abordado aqui, mas você pode explorar nessa direção.

Auto-regressividade e operação vinculada à memória

Como podemos ver, a parte de geração de saída de grandes modelos de linguagem é de natureza auto-regressiva. A indicação para que qualquer novo token seja gerado depende de todos os tokens anteriores e de seus estados intermediários. Como no estágio de saída nem todos os tokens estão disponíveis para fazer cálculos adicionais e ele tem apenas um vetor (para o próximo token) e o bloco do estágio anterior, isso se torna como uma operação de vetor de matriz que subutiliza a capacidade de computação da GPU quando em comparação com a fase de pré-preenchimento. A velocidade com que os dados (pesos, chaves, valores, ativações) são transferidos da memória para a GPU domina a latência, e não a rapidez com que os cálculos realmente acontecem. Em outras palavras, esta é uma operação vinculada à memória .

Soluções inovadoras para superar desafios de produtividade

Lotes contínuos

A etapa muito simples para reduzir a natureza vinculada à memória do estágio de decodificação é agrupar a entrada e fazer cálculos para múltiplas entradas de uma só vez. Mas um simples lote constante resultou em baixo desempenho devido à natureza dos diferentes comprimentos de sequência sendo gerados e aqui a latência de um lote depende da sequência mais longa que está sendo gerada em um lote e também com o aumento da necessidade de memória, uma vez que múltiplas entradas são agora processado de uma só vez.

Portanto, a dosagem estática simples é ineficaz e surge a dosagem contínua. Sua essência reside na agregação dinâmica de lotes de solicitações recebidas, na adaptação às taxas de chegada flutuantes e na exploração de oportunidades de processamento paralelo sempre que possível. Isso também otimiza a utilização da memória agrupando sequências de comprimentos semelhantes em cada lote, o que minimiza a quantidade de preenchimento necessária para sequências mais curtas e evita o desperdício de recursos computacionais com preenchimento excessivo.

Ele ajusta de forma adaptativa o tamanho do lote com base em fatores como capacidade de memória atual, recursos computacionais e comprimentos de sequência de entrada. Isso garante que o modelo opere de maneira ideal sob condições variadas, sem exceder as restrições de memória. Isso ajuda na atenção paginada (explicada abaixo) ajuda a reduzir a latência e aumentar o rendimento.

Leia mais: Como o lote contínuo permite uma taxa de transferência de 23x na inferência LLM e, ao mesmo tempo, reduz a latência p50

Atenção paginada

À medida que fazemos lotes para melhorar o rendimento, isso também tem o custo de aumentar o requisito de memória cache KV, já que agora estamos processando várias entradas ao mesmo tempo. Essas sequências podem ultrapassar a capacidade de memória dos recursos computacionais disponíveis, tornando inviável seu processamento em sua totalidade.

Observa-se também que a alocação ingênua de memória do cache KV resulta em muita fragmentação de memória, assim como observamos em sistemas de computador devido à alocação desigual de memória. vLLM introduziu Paged Attention, que é uma técnica de gerenciamento de memória inspirada nos conceitos do sistema operacional de paginação e memória virtual para lidar com eficiência com a crescente necessidade de cache KV.

A atenção paginada aborda as restrições de memória dividindo o mecanismo de atenção em páginas ou segmentos menores, cada um cobrindo um subconjunto da sequência de entrada. Em vez de calcular pontuações de atenção para toda a sequência de entrada de uma só vez, o modelo concentra-se em uma página por vez, processando-a sequencialmente.

Durante a inferência ou o treinamento, o modelo percorre cada página da sequência de entrada, calculando pontuações de atenção e gerando resultados de acordo. Depois que uma página é processada, seus resultados são armazenados e o modelo passa para a próxima página.

Ao dividir o mecanismo de atenção em páginas, Paged Attention permite que o modelo de linguagem Large lide com sequências de entrada de comprimento arbitrário sem exceder as restrições de memória. Ele reduz efetivamente o consumo de memória necessário para processar sequências longas, tornando viável trabalhar com grandes documentos e lotes.

Leia mais: Fast LLM Servindo com vLLM e PagedAttention

Atenção instantânea

Como o mecanismo de atenção é crucial para modelos de transformadores nos quais os modelos de linguagem grande se baseiam, ele ajuda o modelo a focar em partes relevantes do texto de entrada ao fazer previsões. No entanto, à medida que os modelos baseados em transformadores se tornam maiores e mais complexos, o mecanismo de autoatenção torna-se cada vez mais lento e consome muita memória, levando a um problema de gargalo de memória, conforme mencionado anteriormente. Flash Attention é outra técnica de otimização que visa mitigar esse problema, otimizando as operações de atenção, permitindo treinamento e inferência mais rápidos.

Principais recursos do Flash Atenção:

Kernel Fusion: É importante não apenas maximizar o uso da computação da GPU, mas também fazê-lo funcionar da maneira mais eficiente possível. Flash Attention combina várias etapas de computação em uma única operação, reduzindo a necessidade de transferências repetitivas de dados. Essa abordagem simplificada simplifica o processo de implementação e aumenta a eficiência computacional.

Tiling: Flash Attention divide os dados carregados em blocos menores, auxiliando no processamento paralelo. Essa estratégia otimiza o uso de memória, possibilitando soluções escaláveis ​​para modelos com tamanhos de entrada maiores.

(Kernel CUDA fundido que descreve como o ladrilho e a fusão reduzem o tempo necessário para cálculo, Fonte da imagem: FlashAttention: Atenção exata rápida e com eficiência de memória com reconhecimento de IO)

Otimização de memória: Flash Attention carrega parâmetros apenas para os últimos tokens, reutilizando ativações de tokens computados recentemente. Essa abordagem de janela deslizante reduz o número de solicitações de E/S para carregar pesos e maximiza o rendimento da memória flash.

Transferências de dados reduzidas: o Flash Attention minimiza as transferências de dados entre tipos de memória, como memória de alta largura de banda (HBM) e SRAM (memória estática de acesso aleatório). Ao carregar todos os dados (consultas, chaves e valores) apenas uma vez, reduz a sobrecarga de transferências repetitivas de dados.

Estudo de caso – Otimizando inferência com decodificação especulativa

Outro método empregado para agilizar a geração de texto no modelo de linguagem autorregressivo é a decodificação especulativa. O principal objetivo da decodificação especulativa é acelerar a geração de texto, preservando ao mesmo tempo a qualidade do texto gerado em um nível comparável ao da distribuição alvo.

A decodificação especulativa introduz um modelo pequeno/rascunho que prevê os tokens subsequentes na sequência, que são então aceitos/rejeitados pelo modelo principal com base em critérios predefinidos. A integração de um modelo de rascunho menor com o modelo de destino aumenta significativamente a velocidade de geração de texto, devido à natureza do requisito de memória. Como o modelo preliminar é pequeno, não requer menos. de pesos de neurônios a serem carregados e o não. de computação também é menor agora em comparação com o modelo principal, o que reduz a latência e acelera o processo de geração de saída. O modelo principal então avalia os resultados gerados e garante que eles se encaixem na distribuição alvo do próximo token provável.

Em essência, a decodificação especulativa agiliza o processo de geração de texto, aproveitando um modelo de rascunho menor e mais rápido para prever os tokens subsequentes, acelerando assim a velocidade geral da geração de texto, mantendo a qualidade do conteúdo gerado próximo à distribuição alvo.

É muito importante que os tokens gerados pelos modelos menores nem sempre sejam rejeitados constantemente, este caso leva à diminuição do desempenho ao invés da melhoria. Por meio de experimentos e da natureza dos casos de uso, podemos selecionar um modelo menor/introduzir decodificação especulativa no processo de inferência.

Conclusão

A jornada pelas técnicas avançadas para aprimorar o rendimento do Large Language Model (LLM) ilumina um caminho a seguir no domínio do processamento de linguagem natural, mostrando não apenas os desafios, mas também as soluções inovadoras que podem enfrentá-los de frente. Essas técnicas, desde lotes contínuos até atenção paginada e flash, e a abordagem intrigante da decodificação especulativa, são mais do que apenas melhorias incrementais. Eles representam avanços significativos em nossa capacidade de tornar grandes modelos de linguagem mais rápidos, mais eficientes e, em última análise, mais acessíveis para uma ampla gama de aplicações.

A importância desses avanços não pode ser exagerada. Ao otimizar o rendimento do LLM e melhorar o desempenho, não estamos apenas ajustando os motores desses modelos poderosos; estamos redefinindo o que é possível em termos de velocidade e eficiência de processamento. Isto, por sua vez, abre novos horizontes para a aplicação de grandes modelos linguísticos, desde serviços de tradução linguística em tempo real que podem operar à velocidade da conversa, até ferramentas analíticas avançadas capazes de processar vastos conjuntos de dados com uma velocidade sem precedentes.

Além disso, essas técnicas ressaltam a importância de uma abordagem equilibrada para a otimização de modelos de linguagem de grande porte – uma abordagem que considere cuidadosamente a interação entre velocidade, precisão e recursos computacionais. À medida que ultrapassamos os limites das capacidades de LLM, a manutenção deste equilíbrio será crucial para garantir que estes modelos possam continuar a servir como ferramentas versáteis e fiáveis ​​numa infinidade de indústrias.

As técnicas avançadas para melhorar o rendimento de grandes modelos de linguagem são mais do que apenas conquistas técnicas; são marcos na evolução contínua da inteligência artificial. Eles prometem tornar os LLMs mais adaptáveis, mais eficientes e mais poderosos, abrindo caminho para inovações futuras que continuarão a transformar nosso cenário digital.

Leia mais sobre arquitetura de GPU para otimização de inferência de modelos de linguagem grande em nossa postagem recente no blog