Weight Folding, Streams CUDA e o Bug Que Fez Meu Modelo Falar ao Contrário — Filip Makraduli

AAI Engineer
Computing/SoftwareInternet Technology

Transcript

00:00:00.
00:00:13Olá a todos, obrigado por virem e
00:00:18vou começar a palestra agora. Então
00:00:22esta palestra gira em torno de um artigo
00:00:27que fiz, que é muito simples.
00:00:33A proposta é muito clara.
00:00:36Basicamente, são duas linhas de
00:00:39álgebra que tornam a camada de normalização RMS
00:00:42em transformers mais barata,
00:00:45mais rápida e que de certa forma a melhoram
00:00:48como uma camada na arquitetura
00:00:51transformer. De forma semelhante a como
00:00:54a normalização de camada costumava ser o padrão
00:00:57e depois foi substituída pela normalização RMS.
00:01:00Isso segue essa mesma linha de raciocínio.
00:01:04E tive a oportunidade de conhecer
00:01:09algumas pessoas do mundo de código aberto
00:01:11e co-criei este artigo
00:01:14junto com Nils Graf, que foi
00:01:17o criador disso.
00:01:21E o trabalho continua a partir daí.
00:01:23Portanto, isto está publicado no arXiv.
00:01:26Vocês podem dar uma olhada, ler, testar.
00:01:29Há também um repositório.
00:01:31E o conceito, digamos, a ideia
00:01:36e a forma de pensar são mais fáceis
00:01:38de explicar usando talvez o FlashAttention.
00:01:40De forma semelhante a como o FlashAttention
00:01:45espera até haver uma multiplicação
00:01:48e tenta limitar essa comunicação
00:01:52entre memórias para que todo o processo
00:01:54seja mais rápido.
00:01:55Este é um pensamento semelhante nessa mesma linha.
00:01:58E faz certas melhorias
00:02:01que tornam o processo da normalização RMS muito mais rápido
00:02:08e, por efeito, melhoram todo o transformer.
00:02:15E uma pergunta é: ok, porquê a normalização RMS?
00:02:19Uma vez que essa camada quase não faz contas.
00:02:24E isso é verdade.
00:02:26Portanto, a fatia da parte matemática,
00:02:29se olharmos para ela, é bastante pequena.
00:02:31No entanto, o tempo de relógio ou tempo de execução,
00:02:34como costumam dizer, é bastante grande.
00:02:36E, por exemplo, num passo de descodificação,
00:02:40ou seja, logo quando a inferência é executada,
00:02:43a normalização RMS pode ser iniciada umas 33 vezes.
00:02:47Claro que depende do modelo e assim por diante.
00:02:50No artigo, têm os modelos específicos
00:02:52e como isto foi testado.
00:02:54E a questão é como isto pode ser melhorado
00:02:59e como esta espera pela multiplicação de matrizes
00:03:03pode ser de certa forma evitada.
00:03:06E a razão pela qual isto é lento
00:03:09é porque as GPUs não são lentas nem fracas a fazer contas,
00:03:15mas são fracas em tudo o resto à volta das contas reais.
00:03:20Ou seja, iniciar o trabalho, o trabalho real.
00:03:24Portanto, por exemplo, iniciar o processo como acontece
00:03:30em algumas das experiências, 33 vezes,
00:03:32isso demora muito tempo.
00:03:35E, por exemplo, fundir cada normalização
00:03:40na multiplicação de matrizes pode ajudar a evitar isto.
00:03:44Além disso, fazer a fusão de pesos
00:03:46pode ajudar a mover dados entre a memória,
00:03:50e esse é um processo que também é lento para as GPUs.
00:03:54E também a espera.
00:03:56Portanto, por exemplo, adiar a divisão
00:04:00que é feita na camada de normalização RMS
00:04:02é também uma forma de evitar este passo de espera.
00:04:06Basicamente, o que este artigo faz
00:04:09é melhorar estes três aspetos
00:04:13ao fazer alguns truques algébricos
00:04:16na forma como a normalização RMS é computada.
00:04:19É isso.
00:04:21E, em termos matemáticos, estes são os truques.
00:04:25Centram-se principalmente nas duas primeiras proposições.
00:04:29Uma é a normalização sem pesos.
00:04:31Podem ver isso aqui.
00:04:33E a normalização adiada.
00:04:35Portanto, essa é a segunda.
00:04:37E agora, em arquiteturas mais recentes,
00:04:39existe uma situação em que o RMS pode aparecer duas vezes.
00:04:44Por exemplo, no Gemma 4 isto acontece.
00:04:48Portanto, cancelar a pré-normalização também funciona.
00:04:52E tudo isto é provado algebricamente no artigo.
00:04:58E a primeira proposição é esta,
00:05:00onde o ganho e a fusão de pesos
00:05:04se fundem numa única matriz.
00:05:06O W que podem ver aqui com um asterisco.
00:05:09E isso é computado fora de linha (offline),
00:05:12de forma semelhante a como no FlashAttention
00:05:14se computam algumas coisas paralelamente
00:05:16para que não haja comunicação
00:05:18entre a memória a toda a hora.
00:05:20Portanto, este é um passo que é feito, por assim dizer,
00:05:24esta fusão de pesos.
00:05:26E o outro passo é adiar a divisão por escalar
00:05:32da multiplicação de matrizes para que possam ser feitas em paralelo.
00:05:35Portanto, num caso normal, teriam de computar uma vez,
00:05:38depois esperar e computar novamente.
00:05:41Neste caso, a ideia é dividir isto
00:05:44para que possa ser paralelizado.
00:05:48E a terceira, que é uma espécie de versão disto,
00:05:51é que existe, caso haja duas,
00:05:56como isto é invariante à escala,
00:05:58uma delas pode ser descartada e continua a funcionar.
00:06:01E isto é aplicável a modelos mais recentes
00:06:04que possam ter esta arquitetura e implementação.
00:06:10Portanto, para tornar isto realidade,
00:06:13especialmente esta proposição número dois.
00:06:16Portanto, para esta, por exemplo, é fácil.
00:06:20Há um repositório chamado Transformer Tricks.
00:06:22Podem simplesmente aplicar isto a qualquer modelo e funciona.
00:06:25Mas para o fazer, há algum trabalho ao nível do kernel.
00:06:29Portanto, não é assim tão simples de fazer.
00:06:31Então, para eu conseguir fazer isso,
00:06:35estive a implementar isto e deparei-me com esta experiência.
00:06:42Parece bem em geral, onde é tipo,
00:06:46ok, o prompt é a arquitetura transformer
00:06:48revolucionou o PLN porque,
00:06:51e depois há algum tipo de saída esperada.
00:06:54Mas na saída que obtive,
00:06:56vi esta repetição e um atraso de um passo.
00:06:59Como podem ver aqui, a palavra porque aparece novamente.
00:07:01E algo estava a acontecer com os fluxos da GPU
00:07:07e eu estava a tentar perceber o que se passava.
00:07:10E estava a obter este atraso de um passo e saídas
00:07:15que eram do passado, de certa forma.
00:07:18E ao depurar tudo isto,
00:07:21percebi que no processo de construir algo assim...
00:07:26Como expliquei na proposição dois, ou ao adiar isto para operações,
00:07:32em CUDA, podem fazer duas coisas.
00:07:35Podem usar núcleos de tensor (tensor cores) que fazem uma parte da multiplicação de matrizes
00:07:39e podem usar núcleos CUDA que executam coisas como operações elemento a elemento,
00:07:44reduções, raízes quadradas, etc.
00:07:47Portanto, a ideia era fazer isto em paralelo e obter o benefício
00:07:52do que estava a explicar no artigo para testar realmente este conceito.
00:07:58Então, era para ser mais ou menos assim.
00:08:00Então, se você faz as coisas sequencialmente,
00:08:03há um tempo de espera ocioso quando a unidade vetorial calcula o RMS e o escalonamento,
00:08:10e então ocorre a multiplicação de matrizes.
00:08:12A ideia era, ok, com o flash norm, que é a técnica do artigo,
00:08:16você deve fazer ambas as coisas em paralelo.
00:08:19Assim, a unidade matricial calcula a multiplicação e a unidade vetorial calcula o RMS.
00:08:23Dessa forma, você economiza tempo.
00:08:26No entanto, você não pode simplesmente fazer isso em Python.
00:08:28Você precisa ir um pouco mais a fundo.
00:08:30E eu fiz isso com códigos CUDA como este.
00:08:35E isso parecia, no geral, OK naquele momento.
00:08:42No entanto, percebi que fiz algo ligeiramente errado.
00:08:47E o problema foi que a junção no final, onde você deveria unir os dois fluxos, era implícita no meu caso.
00:08:57E quando testei isso, o teste unitário funcionou.
00:09:01A qualidade parecia semelhante, como testes de perplexidade e assim por diante, porque é apenas uma geração parecida.
00:09:08Mas, ao gerar textos longos, consegui notar esse problema.
00:09:11Então eu não fazia ideia do que era aquilo.
00:09:14E o motivo era que, ao fazer essa junção implícita, basicamente um dos fluxos ainda não havia terminado o trabalho.
00:09:24Portanto, tive condições de corrida que acabavam lendo dados antigos da multiplicação de matrizes inacabada.
00:09:31Então a ideia que tive para corrigir isso girava em torno do fato de que eu precisava ser explícito sobre a junção.
00:09:40E esperar até que uma das operações termine para ter certeza de que, ao unir, não estou lendo dados antigos.
00:09:48Essa foi a conclusão tirada dessa exploração dos fluxos CUDA.
00:09:54E era assim que eu estava fazendo as coisas.
00:09:57A junção era implícita.
00:10:00Portanto, a etapa de pós-escala lia um valor de buffer antigo.
00:10:06E a forma de corrigir isso é marcando o fim da multiplicação de matrizes,
00:10:14depois marcando o fim do RMS.
00:10:17E então, na pós-escala, aguardar pelo primeiro fluxo.
00:10:21E em seguida, aguardar pelo segundo fluxo.
00:10:24Isso corrigiu o bug e fez o artigo funcionar, fazendo o modelo falar para frente em vez de para trás.
00:10:33Essa foi talvez a perspectiva acadêmica mais legal.
00:10:38Mas eu também queria testar as coisas, certo?
00:10:40Implantar isso, testar, ver como posso fazer funcionar em um ambiente mais voltado para produção.
00:10:47E você também pode ler o artigo e ver todos os testes.
00:10:50Alguns foram feitos, a maioria com modelos LLaMA, mas isso funciona para outras arquiteturas também.
00:10:56Então, o que você pode fazer para este artigo específico é, por exemplo, a dobra de pesos que expliquei, a proposição um, você pode fazer apenas com um código no repositório.
00:11:10É como o flash, você diz flashify e ele faz isso.
00:11:13No entanto, com essa segunda coisa que mencionei, você precisa trabalhar um pouco com kernels se quiser fazer aquilo.
00:11:19Assim como expliquei no meu exemplo.
00:11:22E esses são alguns resultados baseados em modelos LLaMA, e há diferentes tipos de detalhes que você também pode conferir.
00:11:29Como o que acontece se você fizer apenas normalização adiada, o que acontece se você fizer um kernel totalmente fundido.
00:11:36Portanto, há muitos experimentos indo a níveis mais baixos aqui para testar todas as proposições.
00:11:41E estes têm sido os nossos resultados em diferentes, digamos, níveis de escrutínio e detalhe.
00:11:48Mas mesmo o mais simples, como a dobra de pesos, mostra alguma melhoria.
00:11:54E isso também funciona com as ferramentas do dia a dia que você usa em um modelo.
00:11:59Então não é como se você precisasse reinventar a roda ou, sabe, fazer as coisas do zero.
00:12:05Funciona com o torch compile porque é basicamente como um novo ponto de verificação e pronto.
00:12:13O FlashAttention faz truques semelhantes em uma camada diferente.
00:12:16E também funciona com modelos quantizados.
00:12:18Portanto, é super legal aplicar isso e você pode obter um modelo que possui essa nova e interessante camada de normalização.
00:12:27E onde você pode encontrar esses detalhes e códigos para rodar isso é no repositório transformer tricks.
00:12:34Ele contém diferentes truques de álgebra que expliquei, assim como o artigo que mencionei.
00:12:41E há também o repositório de modelos do Hugging Face, onde fiz isso com alguns modelos.
00:12:50E você pode ter um link do Hugging Face para esse modelo e testá-lo.
00:12:54E o que você também pode fazer com esses modelos do Hugging Face é implantá-los em produção.
00:13:00Então, quando pensei em fazer isso, percebi que, ok, agora que digamos que a ciência está pronta e há um link para um modelo do Hugging Face,
00:13:11o motor de inferência Superlink era uma maneira legal de implantar qualquer modelo do Hugging Face.
00:13:19E fizemos isso em hackathons onde as pessoas traziam um modelo personalizado do Hugging Face ou um checkpoint com suas coisas ajustadas.
00:13:27E você pode testar, mesmo se tiver alguma versão desses truques algébricos que queira usar para melhorar um modelo e testar suas próprias ideias de pesquisa,
00:13:37você pode realmente experimentar isso e ter uma versão implantada desse modelo em um cluster, sem precisar se preocupar com códigos de integração para implantar modelos.
00:13:48Isso é bem legal. E o ponto é que, se você tem o cluster completo de código aberto e a inferência de modelos de código aberto,
00:13:58você realmente pode testar esse tipo de ideia de pesquisa mais inovadora, onde, se você quiser fazer manipulação de kernel ou flash norm e coisas assim,
00:14:12é muito mais difícil fazer isso em um endpoint alugado onde você não é dono da inferência.
00:14:18Então, você quer algo que seja portátil e flexível para permitir que faça essas coisas,
00:14:23mas que também seja pronto o suficiente para produção para que você possa testar as coisas em escala.
00:14:27E você pode, por exemplo, usar o Cy para combinar isso com outros modelos.
00:14:33Como você pode ver no canto superior esquerdo, você pode ter esses modelos flashificados combinados com outros modelos para realizar tarefas de agentes, se quiser,
00:14:43e fazer aquele caso de uso maior de ponta a ponta.
00:14:46E a forma como o Cy funciona é que este cluster de produção ajuda você a implantar os modelos.
00:14:52Portanto, você também pode dar uma olhada no repositório do Cy para mais detalhes sobre isso.
00:14:57E há também um mecanismo de fila mais inteligente que ajuda você, especialmente se trabalhar com modelos menores,
00:15:03porque ao fazer as coisas do flash norm, eu trabalhei com modelos LLaMA menores e também com pequenos agentes do Hugging Face.
00:15:11Então, ter uma maneira de implantar modelos menores que também possam rodar na mesma GPU, para que você não precise gastar seu dinheiro com custos de GPU,
00:15:24mas possa alternar os modelos, especialmente os menores, foi bastante útil.
00:15:30E você também pode controlar as configurações do modelo por meio de uma API, bem como o cluster,
00:15:35o que também é bastante conveniente, sem precisar de um especialista em infraestrutura apoiando sua pesquisa de código aberto.
00:15:40Então isso também é legal.
00:15:43E você é dono da sua nuvem, o que é útil se você quer pesos abertos, modelos abertos e código aberto.
00:15:50E há também um catálogo que o Cy possui com diferentes modelos, não apenas os que mencionei,
00:15:57mas você pode dar uma olhada.
00:15:59Há também modelos de re-ranking e embedding, caso você esteja construindo algo nessa linha.
00:16:03E com isso, encerro esta história da minha jornada de pesquisa, onde co-lumperei este artigo sobre a técnica que melhora o transformador,
00:16:15mas também encontrei uma forma de levar isso para a produção, testar e achar um jeito de brincar com esses modelos de código aberto.
00:16:24E fiquem à vontade para me contatar no LinkedIn, caso tenham alguma dúvida ou queiram contribuir.
00:16:30Muitas das coisas que mencionei, algumas delas são PRs no VLLM ou no Hugging Face.
00:16:36Você pode encontrá-las por aí.
00:16:38Você também pode conferir o artigo.
00:16:40Esse é o link do arXiv que está aí.
00:16:43E você também tem o repositório do Cy e o meu LinkedIn.
00:16:47Portanto, muito obrigado pela participação.
00:16:58E vocês podem me encontrar para perguntas.
00:16:59Estaremos por aqui.
00:17:00Bem perto.
00:17:13você

Key Takeaway

O artigo Transformer Tricks reduz a sobrecarga de computação da normalização RMS através de truques algébricos e manipulação explícita de fluxos CUDA.

Highlights

  • A camada de normalização RMS em transformers é otimizada por meio de duas linhas de álgebra que fundem pesos e adiam divisões.

  • A normalização RMS pode ser executada até 33 vezes num único passo de descodificação durante a inferência do modelo.

  • A junção implícita de fluxos CUDA causa condições de corrida que provocam atrasos de um passo e leitura de dados antigos na GPU.

  • O repositório Transformer Tricks fornece implementações em código CUDA para testes práticos das proposições algébricas.

  • O motor de inferência Superlink viabiliza a implantação de modelos personalizados e testes de pesquisa em clusters de código aberto.

Timeline

Otimização algébrica da normalização RMS

  • A camada de normalização RMS consome muito tempo de execução apesar da simplicidade matemática.
  • A normalização RMS ocorre cerca de 33 vezes num único passo de descodificação durante a inferência.
  • Truques algébricos reduzem a comunicação entre memórias e aceleram o processo do transformer.

A proposta do artigo consiste em aplicar alterações algébricas para tornar a normalização RMS mais rápida e barata. As GPUs perdem tempo ao iniciar tarefas repetidas vezes e ao mover dados entre memórias. Três proposições matemáticas abordam a normalização sem pesos, a normalização adiada e o cancelamento da pré-normalização em arquiteturas recentes.

Resolução de bugs em fluxos CUDA

  • A paralelização entre núcleos de tensor e núcleos CUDA exige sincronização explícita de fluxos.
  • A junção implícita de fluxos CUDA gera condições de corrida e leitura de dados antigos de buffers.
  • A marcação explícita do fim das operações corrige o problema e restaura a direção correta da geração de texto.

A implementação prática da normalização adiada utiliza núcleos de tensor para multiplicações de matrizes e núcleos CUDA para operações elemento a elemento em paralelo. Uma junção implícita entre os fluxos resulta em leituras incorretas de dados antigos e texto repetido. A correção exige aguardar explicitamente pelo término de cada fluxo individualmente antes da etapa de pós-escala.

Implantação e testes em produção

  • O repositório Transformer Tricks e Hugging Face disponibilizam códigos e modelos para experimentação.
  • O motor de inferência Superlink facilita a implantação de modelos personalizados em clusters de código aberto.
  • A gestão inteligente de filas em modelos menores otimiza custos com recursos de GPU em ambientes de pesquisa.

A aplicação prática das técnicas funciona com ferramentas padrão como torch compile e modelos quantizados. O uso de infraestrutura própria de código aberto elimina a dependência de endpoints alugados. Mecanismos de filas permitem alternar modelos menores na mesma GPU para viabilizar testes complexos de agentes.

Community Posts

No posts yet. Be the first to write about this video!

Write about this video