Pular para o conteúdo

Dois bugs escondidos à vista de todos: uma história de detetives depurando o vLLM — Asaf Gardin e Yuval Belfer

Ver no idioma original →

Autoria: AI Engineer · Assistir no YouTube

Vídeo de autoria do canal indicado, não da IANIA; todos os direitos pertencem ao criador. Transcrição, resumo e tradução gerados automaticamente; o conteúdo no idioma original é preservado na versão em idioma original.

Idioma original: inglês

Vídeo original: Two Bugs That Hid in Plain Sight: A vLLM Debugging Detective Story — Asaf Gardin & Yuval Belfer · Canal: AI Engineer · 17:39 · Assistir no YouTube

Tópicos: Engenharia de software com IA, Inferência e otimização de modelos, Modelos de IA

Asaf Gardin e Yuval Belfer relatam dois bugs silenciosos encontrados durante o treinamento e a inferência do modelo Jamba com Mamba no vLLM. No primeiro caso, o scheduler encaminhava uma solicitação para decode antes de prefill, fazendo com que ela reutilizasse estado de solicitações anteriores. No segundo, um índice de 32 bits sofria overflow sem gerar erro, causando divergências nos logprobs. A palestra destaca técnicas para reproduzir e investigar problemas que produzem resultados incorretos sem falhas explícitas.

  • Bugs em sistemas de inferência com estado podem produzir saídas incorretas sem crash, aviso ou erro visível.
  • Reduzir a GPU memory utilization e aumentar a concorrência ajudou a reproduzir mais rapidamente o primeiro problema.
  • A comparação de logprobs entre o vLLM e o Hugging Face Transformers serviu como ferramenta de investigação.
  • O primeiro bug ocorria porque uma solicitação entrava em decode antes de passar por prefill e usava estado antigo do Mamba.
  • O problema de ordenação afetava o Mamba porque o estado era lido antes de ser sobrescrito, ao contrário do comportamento descrito para attention.
  • No segundo caso, um índice unsigned de 32 bits sofria overflow e voltava ao início sem lançar erro.
  • Aumentar o número de rollouts por prompt fez o segundo bug aparecer mais cedo; reduzir o uso de memória, por outro lado, fez o problema desaparecer temporariamente.
  • Os palestrantes recomendam comparar logprobs com uma implementação de referência, variar as condições de execução e rastrear a identidade das solicitações no código.
Início Capítulo Resumo
00:15 Bugs silenciosos em sistemas de inferência Os palestrantes apresentam problemas de engenharia que geram saídas ruins com alta confiança, sem necessariamente causar erros visíveis.
03:36 Reprodução e investigação do primeiro bug A equipe acelera a reprodução do problema com ajustes de memória e compara os logprobs do vLLM com os do Hugging Face Transformers.
07:14 A solicitação impostora: decode antes de prefill Ao rastrear a identidade da solicitação, a equipe descobre que ela usava estado antigo do Mamba por entrar em decode antes de prefill.
11:34 Segundo caso: divergências recorrentes nos logprobs O aumento de rollouts por prompt ajuda a antecipar a ocorrência de um segundo bug, ligado ao overflow de um índice de 32 bits.
15:27 Correções e lições da investigação Os palestrantes resumem as correções e recomendam comparar logprobs, ajustar as condições de execução e investigar diretamente o código.

Este é um problema de engenharia. — 01:24

Os kernels não estavam fazendo a coisa errada; eles estavam sendo chamados na hora errada para as solicitações erradas. — 10:30

Sistemas de inferência com estado não falham de forma evidente; eles mentem para você com confiança. — 16:01

Não tenha medo de investigar o código e de pôr a mão na massa. — 17:06

  • Asaf Gardin (pessoa): Engenheiro que apresenta a investigação técnica dos dois bugs no vLLM.
  • Yuval Belfer (pessoa): Cocriador da palestra e integrante da equipe que investigou os bugs.
  • AI21 (empresa): Laboratório de pesquisa em IA onde os palestrantes trabalham.
  • Jamba (modelo): Modelo da AI21 descrito na palestra como uma arquitetura híbrida de Transformers e Mamba.
  • Mamba (conceito): Arquitetura baseada em SSM cujo estado e cujos kernels aparecem nos dois casos investigados.
  • vLLM (ferramenta): Engine de inferência em que os palestrantes reproduziram e depuraram os bugs.
  • Hugging Face Transformers (ferramenta): Implementação usada como referência para comparar as distribuições de tokens com as do vLLM.
  • NVIDIA Compute Sanitizer (ferramenta): Ferramenta usada para verificar possíveis problemas de memória, como acessos fora dos limites.
  • logprobs (conceito): Logaritmos das probabilidades dos tokens, usados para comparar os resultados das implementações.

00:15 Então, coloque seu modelo em algum lugar. Coloque seu agent. Você espera que tudo dê certo. Fica esperando algo dar errado. Tudo parece bom. Está tudo bem. E aí você vê isso, e esse é o problema: nesse tipo de bug, não há falha, não há aviso, não há erro, e há alta confiança de que não é um problema de qualidade, porque, certo, isso é algo que…

00:49 …você realmente não sabe o que é, como acontece nem por quê. Bem-vindos a esta palestra. Meu nome é Yuval, este é Asaf e, juntos, vamos levar vocês por uma jornada de como acabamos corrigindo esse tipo de bug. Um pouco sobre nós. Trabalhamos na AI21, um laboratório de pesquisa em IA. Começamos como uma empresa de modelos de base, mais conhecida pelo Jamba, uma arquitetura híbrida entre transformers e Mamba, que é um SSM. E, enquanto…

01:24 …fazíamos isso, enquanto treinávamos esses modelos, enquanto os lançávamos em produção e tínhamos usuários e uma grande carga de trabalho, nos deparamos com vários bugs interessantes. E acho que esses são os bugs mais difíceis de resolver, porque não se trata de um problema de qualidade. Não é algo em que você possa chamar sua equipe de pesquisa para tentar otimizar, resolver ou fazer o modelo ficar melhor em alguma coisa. Este é um problema de engenharia.

01:54 É um problema em que há alta confiança, mas a saída é ruim. Então, vamos analisar a fundo o primeiro caso, que chamamos de solicitação impostora. Para contextualizar, do que estamos falando? Estamos falando de como, durante o treinamento do nosso modelo Jamba, mais especificamente, fizemos GPO, um tipo de treinamento de RL. E, de novo, este é um modelo híbrido, com camadas de Mamba e de atenção.

02:28 E, só para garantir que estamos todos alinhados, qual é o ciclo de vida de uma solicitação? Então, começamos com o prompt e a tokenização e, depois, na passagem forward, fazemos tanto o prefill quanto o decode. Depois disso, terminamos a passagem forward e fazemos a destokenização para voltar ao texto. E o problema aqui é ruim em muitos aspectos, mas principalmente nestes três. É o que chamamos de caso de disparates em uma a cada mil solicitações. Não é…

03:02 …algo que vai acontecer nas primeiras 500 ou 900 solicitações, mas vai acontecer na solicitação número 10.000. É raro o suficiente para ser difícil de reproduzir, mas não tão raro a ponto de ser aceitável. É comum demais para lançar assim. Desculpem. Além disso, só acontecia no vLLM, não em nenhuma outra infraestrutura nem em nenhum outro framework. E é algo que só aparece sob carga. Não vai acontecer se você tiver apenas algumas solicitações. É preciso algum tipo de carga de trabalho. Então, é raro, só aparece sob carga…

03:36 …e é muito específico do engine. É uma tarefa muito, muito difícil, e tivemos que chamar um dos nossos melhores detetives para cuidar disso. Então, vou passar a palavra para Asaf explicar como. >> Certo. Oi, pessoal, obrigado. Obrigado. Então, vamos começar tentando reproduzir algo que era muito difícil de reproduzir. Basicamente, o vLLM tem muitas flags, muitas flags de CLI e muitos parâmetros que vocês podem ajustar. E uma das coisas que nos ajudou a entender como sequer…

04:07 …reproduzir o problema foi perceber que, quando tentamos reproduzi-lo pela primeira vez, apenas enviando prompts aqui e ali, alguns lotes, isso não nos ajudou muito a fazer o modelo retornar disparates. O modelo respondeu normalmente. Então, o que fizemos foi tentar fazer isso acontecer em um intervalo de tempo muito, muito curto. Assim, teríamos um ciclo rápido de feedback ao tentar depurar. O que fizemos foi pegar uma das flags mais padrão e comuns que o vLLM permite ajustar: o uso de memória da GPU.

04:38 …que basicamente permite escolher quanta memória, quanta memória da GPU, você quer alocar para os pesos, para as ativações, para o KV cache e assim por diante. E reduzimos o valor de 90% para 20%. Depois disso, começamos a executar muitas solicitações simultaneamente e, de repente, a solicitação número, digamos, 8.854, retornou disparates. E, quando fizemos isso, coletamos amostras de todos os…

05:08 …lotes com temperature zero. Assim, conseguiríamos fazer a mesma solicitação retornar disparates de maneira determinística e consistente. Como Yuval disse, isso só acontecia no vLLM. E usamos, como outra forma de reproduzir o problema e entender de onde ele realmente vinha, o Transformers da Hugging Face como referência, já que o Transformers tinha uma implementação bem básica e simples dos nossos kernels de Mamba, ao contrário do vLLM…

05:40 …cujos kernels e engine passaram por muitas mudanças e modificações para dar suporte a vários recursos legais do vLLM. Então, usamos o Transformers como referência para entender se havia um problema com nossa inferência ou com o modelo. O que fizemos foi pegar o vLLM e enviar todos os nossos prompts por ele. Geramos as respostas e, agora, tínhamos em mãos as respostas junto com os log probs, porque no vLLM é possível obter os log probs e inspecioná-los. Depois, nós…

06:12 …pegamos a sequência completa, o prompt e a geração, e passamos tudo pela passagem forward da Hugging Face. Mas só executamos o prefill. Em seguida, pegamos os logits da saída do prefill, aplicamos softmax e conseguimos comparar a divergência entre as distribuições dos nossos tokens. Este é um pseudocódigo resumido de como isso fica. Aqui, vocês podem ver que nós…

06:42 …pegamos o prompt e o passamos pelo generate do vLLM. Recebemos a resposta junto com os log probs. Passamos tudo pela passagem forward da Hugging Face. Executamos apenas o prefill. Criamos uma função chamada compute_log_probs, que faz o softmax. Depois, calculamos a diferença entre eles e conseguimos determinar a divergência entre os log probs de cada token. Certo. Agora que temos as ferramentas para entender de onde o problema poderia vir,…

07:14 …começamos a analisar diferentes suspeitos no engine do vLLM. A primeira coisa que examinamos foi o kernel CUDA de prefill do Mamba. Analisamos e inspecionamos toda a matemática feita aqui e ali. Também examinamos os tensores de entrada e de saída, antes e depois de chamar o prefill. Tudo parecia normal. A segunda coisa que fizemos foi executar a ferramenta Compute Sanitizer da NVIDIA para verificar se havia algum acesso à memória fora dos limites ou outros bugs de memória ou…

07:48 …problemas. Para mim, parecia tudo certo. Depois, tentamos isolar os kernels de decode dos kernels de prefill. Vimos que os kernels de prefill estavam funcionando normalmente. Então, tentamos não chamar os kernels de decode, já que no Mamba é possível fazer isso. O que fizemos foi redirecionar todas as chamadas e todos os cálculos para passar pelo kernel de prefill. E, pronto: os disparates desapareceram de repente. Então pensamos: certo, só podem ser os kernels de decode. Mas vocês sabem como é em desenvolvimento de software: a gente fica empolgado…

08:21 rápido demais, e depois você percebe que não foi isso que aconteceu. Então, o que fizemos foi tentar mexer um pouco no engine do vLLM. Meio que precisávamos abrir o capô e ver o que poderíamos fazer para entender um pouco melhor e talvez colocar a mão na massa, porque o vLLM não nos dava mais ferramentas para depurar nosso kernel e nosso forward pass. Então, quando um tensor — quando a solicitação chega até o forward pass e antes de entrar nos kernels de prefill e decode —

08:54 ela não tem realmente uma identidade. Não dá para saber qual prompt está sendo processado naquele momento. São apenas tensores, números e matrizes. Então, o que fizemos foi adicionar o ID da solicitação a uma classe chamada forward context, que propagamos até o forward pass do Mamba, logo antes de os kernels de prefill e decode serem chamados. E ali conseguimos adicionar uma condição if simples com o ID da solicitação que gerava texto sem sentido e colocar um ponto de interrupção. Então, conseguimos

09:26 inferir e inspecionar de verdade todos os metadados associados a ela. Assim que fizemos isso, vimos que, na primeira vez em que a solicitação passou pelo forward pass, ela estava fazendo decode antes de prefill. O scheduler decidiu que essa solicitação deveria fazer decode antes de prefill e, como o Yuval disse antes, no ciclo de vida de um prompt, ele deve passar primeiro por prefill e depois por decode. O que acontecia era que, no Mamba, quando uma solicitação começava com decode, depois de muitas outras solicitações já terem sido computadas, o estado já estava meio que

09:57 sendo usado em excesso, e estávamos usando os dados e os cálculos de solicitações antigas, que tinham chegado antes. Então, estávamos fazendo decode de solicitações anteriores, e isso gerava texto sem sentido. Portanto, os kernels não estavam

10:30 fazendo a coisa errada; eles estavam sendo chamados no momento errado para as solicitações erradas. E por que isso só importava para o Mamba? O motivo é que, na attention, ao gravar os tokens no KV, você os grava antes de fazer a leitura. Então, mesmo que haja dados antigos, eles são sobrescritos. Mas, no Mamba, como eu disse, quando você passa primeiro pelos kernels de decode, primeiro lê o estado e depois faz os cálculos com ele. Então, acabava usando dados antigos ao fazer decode. E a correção foi relativamente simples.

11:01 Só precisávamos garantir que, quando o scheduler classifica uma solicitação pela primeira vez, ele verificasse se os tokens dela ainda não tinham sido computados. Se fosse o caso, nós os marcávamos como prefill. Assim, quando chegassem ao forward pass, seriam usados para prefill, e não para decode nem para chunked. Dá para ver que a alteração foi integrada depois de algum tempo. Isso nos leva a… Então, achamos que estava tudo corrigido, certo? Achamos que estava tudo corrigido. Pronto, não havia mais problemas. Mas quase foi assim, porque, pouco tempo depois, chegamos ao caso número dois, que revelou outro problema que enfrentamos no nosso RL e na nossa inference. Executamos RL e, enquanto rodávamos nossos treinamentos, nossos treinamentos posteriores,

11:34 e analisávamos nossas evals e todos os nossos benchmarks, vimos que havia alguns picos de log-prob entre a etapa de rollout e a etapa de FSDP. E isso acontecia antes de qualquer atualização dos pesos. Portanto, com os mesmos pesos e as mesmas entradas, as duas log-probs deveriam ser idênticas. Mas não eram. Vimos que havia um pico de log-prob a cada 12 etapas, de forma constante. Isso era meio estranho. O que vocês fariam, certo? Qual seria a primeira coisa a fazer? Queríamos encontrar uma variável que mudasse

12:08 a forma como as coisas falham, e não apenas o quanto elas falham. Queríamos ajustar alguns parâmetros que não se limitassem a nos dizer: “Ei, este erro é muito, muito grave. Este erro acontece tantas vezes”, e assim por diante. Queríamos ajustar alguns parâmetros que nos mostrassem que, ao mexer neles, entenderíamos como estavam conectados a alguma coisa no engine do vLLM. Assim, conseguiríamos investigar e depurar especificamente aquela parte. Esse é um meme legal que vocês

12:42 quiseram colocar. Então, decidimos aumentar a quantidade de rollouts por prompt. Como vimos que, no nosso engine de RL padrão, tínhamos oito rollouts por prompt e que o problema acontecia de forma determinística a cada 12 etapas, pensamos: “Vamos tentar aumentar um pouco e elevar a quantidade de rollouts por prompt.” Então, começamos a dobrar o número: de, por exemplo, oito para 16, 32, 64 e 128. E dá para ver que há quase um padrão:

13:16 quanto mais aumentávamos esse número, mais cedo o problema acontecia. O que queríamos alcançar era reproduzir o problema o mais rápido possível, para ter um ciclo de depuração e feedback mais rápido. Quando executamos com 128 rollouts por prompt, o problema aconteceu imediatamente, na etapa 1, e não precisamos esperar até as etapas 12, 24 e assim por diante. Agora, vocês talvez pensem: “Certo, vocês mexeram na utilização da memória da GPU antes de ajustar isso; vocês a reduziram. Parece que, quando testamos sob

13:51 pressão, as coisas aparecem.” Nós também pensamos isso. E, quando reduzimos a memória da GPU de 0,9 para 0,2, o problema desapareceu. Então, na verdade, mexemos na variável errada. O motivo é que percebemos que os kernels do Mamba usavam um padrão de ponteiros com índices unsigned de 32 bits. Então, quando o deslocamento passava de uns 4 bilhões de números, ele dava a volta em vez de gerar um

14:22 erro. Quando reduzimos a memória da GPU, o vLLM alocou um buffer de estado menor, e o índice do cache nunca ficou grande o bastante para atingir aquela posição. Então, simplesmente não avançávamos o suficiente no buffer para provocar um overflow. De novo, a correção foi bem simples. Só precisávamos mudar uma palavra: uma variável de tipo de dado, de uin32 para size_t, que, na maioria das arquiteturas de hardware modernas,

14:55 faria com que size_t passasse a ser unsigned de 64 bits, um número muito grande. Nunca chegamos a esse número, e esse overflow deixou de acontecer. O que podemos ver aqui é que tivemos duas cenas e um criminoso. Os dois casos tinham sintomas parecidos: texto sem sentido sem nenhum aviso e picos silenciosos de log-prob, que às vezes também geravam texto sem sentido. Ambos envolviam o cache de estado do Mamba. Os dois foram revelados pela pressão de memória, seja…

15:27 size_t significaria que ele seria alterado para um inteiro sem sinal de 64 bits, e esse é um número muito grande. Nós nunca chegamos a esse número, e esse overflow nunca aconteceu. Então, o que podemos ver aqui é que tivemos dois casos e um criminoso. Ambos tinham sintomas semelhantes. Nos dois, havia texto sem sentido silencioso e picos silenciosos de logprob, que também às vezes provavelmente geravam texto sem sentido. Ambos estavam relacionados ao cache de estado do Mamba. Ambos vieram à tona por causa da pressão de memória, seja ela

16:01 Se foi para pior ou para melhor, ambos descobriram isso por meio de uma análise forense dos logprobs. Sistemas de inference com estado não falham de forma explícita; eles mentem para você com confiança. Quer dizer, obviamente, às vezes ocorre uma falha, você recebe erros de acesso fora dos limites, outras exceções e assim por diante. Mas, às vezes, há erros que não aparecem: você não recebe um trace log, não recebe nada. Precisa investigar e entender por que as coisas aconteceram. Então, se há alguma lição a tirar desta apresentação, é criar um script para comparar logprobs.

16:35 Se você precisa comparar a qualidade, precisa fazer essa comparação para entender se está modelando os problemas ou não. Um script para comparar logprobs com uma baseline de outro framework de inference que você tenha ou tenha criado é sempre ótimo. Tente reproduzir o problema sob restrições de memória. Aumente a escala, experimente outros parâmetros que o framework de inference oferece e tente realmente entender de onde vem o problema. Procure o que altera o padrão da falha, o tempo, o espaço e a localização. E, quando as coisas não…

17:06 …têm realmente uma identidade, propague a identidade da thread. E o que também quero que vocês levem desta apresentação: não tenham medo de investigar o código, mesmo em sistemas complexos como VLMs ou qualquer outro framework complexo. Não tenham medo de se aprofundar e pôr a mão na massa. Às vezes, modelos de linguagem, LLMs, podem dizer como as coisas funcionam, mas, sem ver com os próprios olhos e pôr a mão na massa, vocês não vão entender completamente o que está acontecendo. Obrigado. Vocês podem nos adicionar no LinkedIn. Leiam o QR…

17:39 …code para ler o artigo que publicamos com essas descobertas. É isso. [aplausos]

Transcrição original

[00:15] So, put your model somewhere. Put your agent. You’re hoping for the best. You’re waiting for something to crash. Everything looks good. Everything’s fine. and you see this and that’s the problem right in these type of bugs there is no crash there’s no warning no error and there’s high confidence that’s not a quality issue because uh right this is something that

[00:49] you don’t really know what and how and why um welcome to this talk my name is Ival this is aaf and together We’re going to take you on a journey of how we ended up fixing those type of bugs. A little bit about us. We work at AI21, which is an AI research lab. We started as a foundation model company, most famously known for Jamba, which is a hybrid architecture between transformers and mamba, which is an SSM state. And while

[01:24] we were doing those, while we were training those models, while we were shipping those models into production and had users and we had a lot of workload, we got into several interesting bugs. And these are the bugs that I think are the hardest to deal with because this is not a quality problem. It’s not something you can take your research team and try to optimize or solve or make the model be better at something. This is an engineering

[01:54] problem. This is an issue where that there is high confidence but the output is bad. So let’s break let’s dive deep to the first case what we call the imposter request where just to set up the scene what are we talking about we are talking about how we during the training of our jamba model more specifically we did gpo which is a type of RL training and again this is a hybrid model layers of mamba and attention

[02:28] and just to make sure we’re all aligned what is the type of a request So life lives life of a request. So we start with the prompt token tokenization and then in the forward pass we’re doing both prefill and then decode. After that we finish the forward pass detoenization to go back to text. And the thing about the crime here is that it’s bad on so many levels but mainly on these three. This is what we call the one in thousand gibberish. It’s

[03:02] not something that will happen in the first 500 or 900 requests, but it will happen in the 10,00 which is rare enough to duplicate it easily but not right. It’s too common to ship it. Sorry. H also it only happened in VLM not in other not in any other infra framework and it’s something which is laid on set. It’s not something that will happen if you have only few requests. You need some sort of workload. So it’s rare. It’s light on

[03:36] set and it’s very engine specific. It’s a very very hard task and we had to bring one of our best detectives to handle that. So I’ll give it to SF to explain how. >> All right. Hey guys, thank you. Uh thanks you. So we’re going to start with um trying to reproduce something that was very difficult to reproduce. Um and basically um VLM has got a lot of um a lot of flags, a lot of the CLI flags and a lot of knobs you guys can turn and tweak. Um and one of the things that helped us understand how to even

[04:07] reproduce it because when we try to reproduce it on the first time just sending prompts here, prompts there a few batches um it didn’t really help us manage to get gibberish back from our model. The model responded back just fine. So what we did was is we tried to make it happen in a very very short um amount of time. So we’ll get a a quick feedback loop when we try to deb debug it. So what we did was is we took one of the one of the um most default and most common um um flag that the VLM allows you to play with which is GPU memory

[04:38] utilization which basically allows you to to choose how much memory how much GPU memory you want to allocate for your for your weight for your activations and for your KV cache and so on. and we reduced it from 90% to 20%. And once we did that and then we started uh running a lot of requests uh simultaneously um all of a sudden request number let’s say 8 854 suddenly returned gibberish and when we did that we uh we sampled all of the

[05:08] batches with temperature zero. So we’ll be able to deterministically and constantly get the same request to get to return to return and respond with gibberish. Um so um like Ival said it happened only in VLM and um we used um in order to uh another way to reproduce it and to understand where the issue really came from. We used um uh hugging faces transformers as a baseline since transformers is a very had a very um vanilla and uh plain implementation of our mamba kernels as opposed to VLM

[05:40] which uh all the kernels and all the engine have gone through a lot of changes and modifications to support a lot of um cool features that VLM supports. So we use transformers as our baseline to understand whether or not there is an issue with our um with our inference or not with the model or not. So what we did was we took VLM and we sent uh all of our prompts through VLM and we generated a response all the responses we got and now in our hands we have the response along with the log props because because in VLM you’re you able to get your log props out and inspect them. Then what we did was we

[06:12] took the full sequence the prompt and the generation and we uh uh passed it over to to hugging faces forward pass but all we did was run just the prefill um and then we uh uh samp we took this the logit out of the prefill um um response we ran it through softmax and then we were able to u to compare the divergence uh in the distributions of our tokens. Um that’s a short uh pseudo code of how that look like. You can see here that we

[06:42] uh take up the prompt. We we run it through gen uh BLM’s generate. We get the uh the response back along with the uh with the log props. We pass it over to to hugging faces forward pass. We only run it with prefill. Um we’ve created some function called compute log log props which um runs this is the softmax. Um then you calculate the difference between them and then you’ll be able to tell the divergence between every one of the tokens log props. All right. So now that we have the tools in our hand to understand where the issue

[07:14] could maybe come from um we started to look at different suspects in VLM’s engine. So the first thing we looked at was um the CUDA prefill kernel of Mamba. Um we looked at it we inspected all of the all the math that’s being done here that’s being done there. Um, and we looked at the tensor in, and the tensor’s out before before we called the prefill and after. Everything looks just fine. Second thing we did was running Nvidia’s compute sanitizer tool to really see if we have any out of bound memory. Um, any other memory uh bugs or

[07:48] issues. Looked okay to me. Um, then what we did was we tried to isolate between the decode kernels and the pre-filled kernels. Now, we saw that the pre-filled kernels were working just fine. So we tried to not call the decode kernels because in Mamba you’re able to do that. Um so what we did was um we moved all of our calls and all of our computations to go through the pre through the pre-fill kernel and there you have it. The gibbish all of a sudden kind of vanished. So we were like okay it’s got to be the decode kernels. But you know how it is in software you get excited

[08:21] too quickly and then you figure out it’s not what happened. So what we did was uh we tried to start playing with the with VLM’s engine and we kind of needed to go and you know lift the hood up and see what we can do to maybe get a bit better understanding and maybe you know get our hands dirty because BLM didn’t really give us more um tools to really um debug our kernel and our and our forward pass. So once so once uh once a tensor once the request gets all the way to your forward pass and before it goes into your prefill and decode kernels you

[08:54] don’t really have any identity. You can’t really tell what prompt uh is currently being processed. It’s all just tensors and and numbers and matrices. So what we did was is we added to the request ID to some class called forward context that uh that we propagated all the way down to uh to Mamba’s forward pass just before the the prefill and the decode kernels were called. And there we just managed to uh you know have a simple uh if condition with a request ID, the one that gave us gibberish and put a break point there and then we were

[09:26] able to to to infer and to really um inspect all the metadata that comes along with it. And the second we did that, we saw that the request was um for the first time when it went through the uh through the forward pass, it’s actually doing decode before prefill. the scheduler um decided that it’s um that that this request should be doing decode before prefill and as Uval said earlier in our in the life cycle of a prompt a prompt should first be uh going

[09:57] through prefill and then decode and what happens was is that when when in Mamba um you run a request first uh with a with decode first after a long a lot of other requests were already computed the state was already kind of um overused and we were using uh the data and the computations of stale requests, requests that came before it. So now we were actually running decode on on previous requests and that kind of generated gibberish for us. So the kernels weren’t

[10:30] doing the wrong thing, they were called at the wrong time for the wrong requests. And now and why did it matter only for Mamba? The reason is was is that in uh in attention uh when you write the tokens KV you write it you write the tokens cavies before you actually you read it. So even if you have stale data it’s being overwritten but for mamba as uh as I said when uh when you first go through the decode kernels you first read the state and then you compute over it. So what happens was is you just use over you use

[11:01] stale data um um when you do the when you do a decode and the fix was relatively simple. Well, we just needed to make sure that what we do is that when a request first when the when theuler first classifies a request, it’s got to it’s got to make sure that um that um that it sets that that if a that if that if you sees a request that’s whose tokens were never been computed. Um we and and they’re zero to mark them as to mark them as uh as prefill as as prefill. So the so when they get to the forward pass, they’ll actually just be

[11:34] um uh used for prefill and not decode and not u and not chunked. You can see that it was merged after some time. Um and that really leads us to and then we thought everything was fixed, right? We thought everything was fixed and there you have it. No more issues. But that but that was almost the case because after a little bit of time it gets us to case number two which um surfaced another issue that we’ve faced in our in our RL and our inference. Um so we ran RL and and when while we were running um our our trainings our post trainings um

[12:08] and we looked at our evaluations and all of our benchmarks we saw that we had some log prop spikes between the rollout and the FSTP step. Uh so and that was before any weight app update. So some weights uh same inputs and the two log props should be identical. Now they weren’t. Um we saw that every 12 step cons constantly um there was a log prop spike and that was kind of weird. Now what would you guys do right? What can what what’s the what’s the first thing to do here? So we wanted to find some lever that changes

[12:42] how things fail and not just how much they fail. We want to see how much um we want to tweak some knobs that that don’t just tell us, hey, this error um this error is very very bad. This error happens uh this many times or or [clears throat] and so and so on. We wanted to tweak some knobs that kind of tell us that once we tweak that knob, um we understand how it’s wired to uh anything in the in in VLM’s engine. And so we’ll be able to specifically go and and debug that specific part. Um that’s some uh some some cool meme that you all

[13:16] wanted to put in. Um so what we did was is we decided to um to increase the the the amount of rollouts per prompt. Uh since we saw uh in our default RL engine we have um eight rollouts per prompt and we saw that it happened con deterministically every 12 steps. We decided okay let’s try to tweak it up a bit and uh and increase the amount of rollouts per prompt. So we started doubling it from say eight to 16 to 64 32 and 128. And you can see here that it’s almost um almost um there’s a pattern here that

[13:51] that the more we increased it the closer um it happened because what we wanted to achieve here we wanted to try to reproduce the issue as fast as possible so we’d have a faster debug uh debug loop feedback loop. So when we when we when we ran it on 128 rollouts per prompt, it happened immediately on step one and we didn’t have to wait for step 12 and step 24 and so on. Now you might think, okay, so you guys played with the with the GPU memory utilization before you you tweaked it, you decreased it. It looks like, you know, when you test on

[14:22] pressure, um, it really surface things up. So So we we thought that as well. And when we reduced the GPU memory from zero from 0.9 to 0.2, two, it actually caused the issue to go away. So, we actually pulled the wrong lever here. Um, and the reason is is because we noticed that Mamba kernels um used um in 32 unsigned in 32 index pattern uh pointer. So, once the offset went past some you know 4 billion um uh numbers, it wrapped around instead of throwing an

[14:55] error. Um so uh when we shrank the GPU memory, VLM allocated a small state buffer and um and the cache index never got large to hit that slot. So we were so we were just not reaching um far enough for the buffer to trigger an overflow. So again the fix was rather simple. All we needed to do was just change one word, one one data type variable um from u in32 to size t which basically means for most modern architectures hardware architectures

[15:27] size t would mean to uh it would be now changed to unsigned 64 uh bit and that’s a very large number. We didn’t we never reached that number and that overflow now never happened. So what we can see here is that we had um two scenes and one criminal. Um both kind of you know they had similar symptoms. Both had silent gibberish and and silent log prop spikes which also kind of pro sometimes generated gibberish. They were both around the mamba state cache. Um they were both surfaced by memory pressure whether it

[16:01] was for worse or for the best and uh both found via log props um forensics. um stateful inference inference systems don’t fail loudly they lie to you confidently I mean obviously sometimes you get crash you get out of bounds errors you get other you know exceptions and so on but sometimes there are some errors that don’t surface up and you don’t get a trace log you don’t get anything you have to go and dig and understand why things happen um so if there some takeaways to take from this um presentation is build a log props

[16:35] comparison script if you need to compare your quality you need to compare it to understand whether you modelize the issues or not. Um log props comparison script with a baseline of some other inference framework that you have or built is always great. Um reproducing underression constrain memory. Uh crank the scale up, play with other knobs that the inference framework gives you and really try to understand where the issue comes from. Uh look for what moves um the failure shape, the timing, the space and the location. And when things don’t

[17:06] really have identity uh thread identity through and what it also I want you to take from this and don’t be afraid to even you know for complex uh systems like VLM or any other um complex framework don’t be afraid to go dig in the code get your hands dirty um sometimes you know model languages LLMs are um they might tell you how things work but you know without you seeing it in your own eyes getting your hands dirty you won’t get full understanding of what’s going on. Um, thank you. You guys can add us on LinkedIn. Scan the QR

[17:39] code to read the actual blog that we’ve published with this finding. Um, yeah, that’s it. [applause]