Elevata

Artigo

Inferência NVFP4 em GPUs Blackwell SM120: vLLM, FlashInfer e o que funcionou

Bruno Machado Valerio
Ver perfilPublicado 3 de junho de 202614 min de leitura

Novas arquiteturas de GPU costumam ser vendidas como upgrades simples. Para times de inferência, a realidade é mais complicada.

Este post compartilha notas de campo generalizadas sobre colocar um modelo de linguagem grande quantizado em NVFP4 em produção em GPUs Blackwell SM120. O trabalho original veio de uma implantação real, mas os detalhes aqui foram deliberadamente anonimizados: sem nome de cliente, sem nome exato de modelo, sem IDs de conta, sem tags internas de imagem, sem datas de benchmark e sem identificadores de trace de produção.

A versão curta:

SM120 funcionou bem depois que a stack de serving foi montada com cuidado, mas ainda não era um caminho padrão sem surpresas.

A receita estável exigiu um build recente do vLLM, suporte ao FlashInfer b12x, configuração explícita para SM120, pesos ModelOpt NVFP4 no modelo-alvo, KV cache em FP8 e separação cuidadosa entre o modelo-alvo e o drafter especulativo.

A lição mais importante não foi uma versão de pacote ou flag de runtime. Foi uma fronteira:

O contrato de quantização NVFP4 do modelo-alvo não pode vazar para um modelo drafter (draft model) que não seja NVFP4.

Limite das afirmações

Estas são notas de campo generalizadas de uma implantação anonimizada, publicadas em 3 de junho de 2026. Não são uma receita universal para SM120 nem um guia congelado de versões de pacote. As lições duráveis são sobre o contrato de serving: alvo de GPU, build do vLLM, suporte FlashInfer/CUTLASS, formato do checkpoint ModelOpt NVFP4, dtype do KV cache, configuração do drafter especulativo, comportamento de proxy/gateway, tracing e benchmark reproduzível.

Antes de copiar qualquer flag ou receita de imagem, valide contra seu SKU exato de GPU, versão do CUDA, versão do vLLM, versão do FlashInfer, checkpoint do modelo e caminho real das requisições.

Afirmações seguras e inseguras

As afirmações reutilizáveis são:

  • Checkpoints NVFP4 grandes podem rodar em SM120 com um stack vLLM recente e suporte ao FlashInfer b12x.
  • SM120 deve ser configurado explicitamente; configurações padrão de arquiteturas mais antigas não bastam.
  • Pesos ModelOpt NVFP4 no alvo funcionaram com KV cache em FP8 neste setup de serving.
  • A decodificação especulativa funcionou uma vez que o drafter foi configurado como uma identidade de modelo separada, com sua própria precisão e layout.
  • A configuração de quantização NVFP4 do modelo-alvo não pode vazar para um drafter não NVFP4.
  • A melhoria reprodutível de decodificação especulativa foi substancial, mas menor do que os primeiros picos observados.
  • Teste de carga com perfil de produção revelou características de estabilidade que prompts de brinquedo não teriam mostrado.
  • Tracing de benchmark deve ser habilitado antes de execuções importantes, não reconstruído depois.
  • Saúde de runtime, saúde de proxy e saúde de gateway precisam ser validadas separadamente.

A afirmação insegura seria:

Este stack entrega o pico inicial de taxa de tokens como performance em regime estável.

Ele atingiu números mais altos por pouco tempo. Essas observações foram reais, mas não foram reprodutíveis o suficiente para virar manchete.

Por que isso não foi apenas mais uma migração de GPU

Uma migração típica de inferência começa com uma suposição simples: se o modelo roda em uma GPU NVIDIA recente, deve rodar na próxima com apenas pequenos ajustes.

Essa suposição não se sustentou aqui.

A implantação combinou várias peças em movimento ao mesmo tempo:

  • Nova arquitetura de GPU
  • Pesos NVFP4 no modelo-alvo
  • Serving com contexto longo
  • KV cache em FP8
  • Kernels do FlashInfer
  • Decodificação especulativa
  • Roteamento de requisições compatível com OpenAI
  • Tool calling e comportamento de parser

Cada peça pode funcionar isoladamente. A dificuldade é fazer com que todo o caminho de serving concorde sobre kernels, metadados do modelo, layout de quantização, formato de cache e semântica de requisição.

A primeira lição prática foi:

Trate SM120 como um alvo de primeira classe, não como uma substituição transparente para arquiteturas NVIDIA mais antigas.

As configurações padrão antigas não foram suficientes. O runtime e o stack de kernels precisavam saber explicitamente que o alvo era compute 12.0f / 120f.

SM120 não significa “qualquer Blackwell”.

O alvo importante aqui era SM120 / compute 12.0. Não assuma que toda GPU Blackwell, toda imagem CUDA ou todo pacote de kernels pré-compilado usa o mesmo alvo de arquitetura. Peças de datacenter Blackwell usam compute capabilities diferentes das classes SM120 de workstation/desktop. Antes do benchmark, registre o SKU exato da GPU, compute capability, versão do CUDA, linhagem da imagem de runtime, pacote de kernels e caminho de roteamento.

Escolha de runtime: use o caminho que de fato sobe

Avaliamos mais de um caminho de serving. Runtimes otimizados de mais baixo nível são atrativos para inferência NVFP4 e podem, no futuro, se tornar a melhor opção à medida que o suporte a SM120 amadureça.

Para esta implantação, contudo, o caminho prático foi vLLM com uma imagem customizada.

A pergunta decisiva não foi:

Qual runtime deveria ser o mais rápido em teoria?

Foi:

Qual runtime consegue carregar os pesos quantizados, escolher os kernels corretos, lidar com o perfil real das requisições, sobreviver a health checks/probes repetidos e permanecer depurável quando algo falha?

Para alvos de hardware novos, essa distinção importa. Um runtime teoricamente mais rápido não é útil se não consegue inicializar o modelo de forma confiável ou suportar o caminho de API de produção.

A receita da imagem: torne SM120 explícito

As imagens de release padrão não foram suficientes. A imagem que funcionou partiu de uma imagem recente do vLLM compatível com OpenAI e adicionou configuração explícita do FlashInfer para SM120:

FROM vllm/vllm-openai:nightly

ENV FLASHINFER_CUDA_ARCH_LIST=12.0f \
    FLASHINFER_FORCE_SM=120f \
    FLASHINFER_DISABLE_VERSION_CHECK=1

Sobre FLASHINFER_DISABLE_VERSION_CHECK=1: use somente quando você já tiver verificado separadamente o caminho de wheel, cubin e cache JIT para sua GPU alvo. Não é conselho genérico. Essa flag silencia uma trava de segurança que existe por um motivo, e só é segura quando você já sabe o conjunto de kernels com o qual está rodando.

A imagem também precisou de componentes recentes de CUTLASS e FlashInfer, incluindo wheels nightly, cubins e suporte a cache JIT para a geração de CUDA correspondente.

As versões exatas dos pacotes vão mudar rapidamente, então a lição duradoura não é “copie esta imagem para sempre”.

A lição duradoura é:

Não assuma que um alvo de GPU novo está incluído em toda imagem de release, wheel pré-compilado, cache de kernel ou pacote de cubin.

Se o stack silenciosamente cair para kernels não suportados, ou se os cubins corretos estiverem faltando, as falhas resultantes podem parecer alheias ao suporte da arquitetura. Você pode ver erros de inicialização, checagens de versão, incompatibilidades de shape ou falhas de runtime que na verdade são sintomas de um caminho SM120 incompleto.

Houve também uma sutileza de container runtime: a mesma imagem pode se comportar de forma diferente dependendo do runtime do nó e da configuração de usuário do container. Valide a imagem sob a mesma camada de orquestração, mesmo caminho de plugin de GPU e mesmo usuário de runtime que a produção vai usar.

Sucesso em Docker local não é o mesmo que sucesso em deploy de produção.

NVFP4 subiu antes da decodificação especulativa

A primeira faixa estável foi inferência NVFP4 sem decodificação especulativa:

  • Pesos do alvo: checkpoint ModelOpt NVFP4
  • Quantização: ModelOpt
  • KV cache: FP8
  • Backend de GEMM: caminho FlashInfer CUTLASS NVFP4

Uma configuração de runtime representativa ficou assim:

--enable-chunked-prefill
--enable-prefix-caching
--quantization=modelopt_fp4
--kv-cache-dtype=fp8

Confirme a flag de quantização correta do vLLM para a versão instalada. A documentação atual do vLLM identifica modelopt_fp4 para checkpoints ModelOpt NVFP4; snippets mais antigos e algumas builds nightly podem aceitar modelopt como atalho. Valide contra a versão exata do vLLM que você está rodando antes de copiar essa configuração.

A implantação também usou configurações de contexto longo e limites delimitados de batch/concorrência apropriados para o workload. Esses valores devem ser ajustados por ambiente em vez de copiados diretamente.

Esse baseline foi estável, mas não entregou o alvo completo de performance. Para gerações curtas e isoladas, o caminho não especulativo ficou na faixa baixa dos 30 tokens/s neste setup.

Isso tornou a decodificação especulativa a próxima alavanca óbvia.

A decodificação especulativa expôs o bug real

A falha mais interessante apareceu somente depois que a decodificação especulativa entrou em cena.

O modelo-alvo era NVFP4. O modelo drafter (draft model) não era.

Essa distinção é fácil de perder.

Em muitos setups de decodificação especulativa, o modelo-alvo e o drafter são tratados como muito próximos. Eles podem compartilhar um tokenizer ou uma família de arquitetura. Mas não são necessariamente o mesmo artefato de modelo. O drafter pode usar precisão diferente, layout de checkpoint diferente, contrato de metadados diferente ou shape de pesos diferente.

O caminho quebrado, na prática, reutilizou a configuração de quantização NVFP4 do alvo para o drafter. Isso produziu incompatibilidades de shape e falhas de assertion durante a inicialização.

A correção foi conceitualmente simples, mas importante:

Configure o drafter como uma identidade de modelo própria, com sua própria precisão e seu próprio layout.

O formato que funcionou foi:

  • Modelo-alvo: checkpoint NVFP4
  • Drafter: checkpoint de assistente/draft separado
  • KV cache: FP8
  • Janela de draft: número fixo pequeno de tokens especulativos
  • Backend: caminho FlashInfer CUTLASS NVFP4

Essa é a lição técnica mais reutilizável da implantação.

Qualquer modelo-alvo NVFP4 grande usando decodificação especulativa pode bater nessa classe de problema se o drafter não for, de fato, a mesma arquitetura e o mesmo layout quantizado.

Mapa de modos de falha

A forma mais útil de ler o resto deste post é como um conjunto de modos de falha que você consegue reconhecer rápido, validar e direcionar para a correção certa.

SintomaCausa provávelPasso de validaçãoCorreção
Erros de inicialização, checagens de versão, incompatibilidades de shape no carregamento do modeloImagem padrão não tem como alvo compute 12.0 / 120fInspecione FLASHINFER_CUDA_ARCH_LIST e o conjunto de cubins na imagemConstrua uma imagem com SM120 explícito, configuração correta do FlashInfer e wheels/cubins verificados
Fallback silencioso de kernel ou throughput degradadoWheels ou cubins para SM120 ausentesConfirme a presença de kernels NVFP4 CUTLASS para SM120 na imagem em execuçãoAtualize wheels/cubins; não dependa apenas de desabilitar checagens de versão
Incompatibilidade de shape ou falha de assertion ao inicializar decodificação especulativaConfiguração de quantização NVFP4 vazou do alvo para um drafter não NVFP4Inspecione precisão, layout e metadados do drafter de forma independente do alvoConfigure o drafter como identidade de modelo separada, com precisão e layout próprios
Runtime direto saudável, gateway retorna errosRota, endpoint picker, registro de serviço ou mapeamento de modelo ausenteChame o modelo via runtime, proxy interno e gateway externo separadamenteTrate cada camada como uma superfície de saúde própria; corrija a entrada de roteamento ausente
Autoscaler mantém capacidade limitada após scale-upLimites de recurso foram zerados via patch em vez de removidosReveja os campos de limite de GPU, não apenas o estado nominal do podRemova os campos inteiramente em vez de sobrescrever com um objeto vazio
Não é possível reconstruir um benchmark histórico com alta fidelidadeTracing não foi habilitado antes da execuçãoVerifique a variável de ambiente de tracing e a configuração de success-callback do proxyHabilite tracing antes da próxima execução; não dependa de logs de spend como fonte de replay

Semântica de parser faz parte do serving

Geração crua de tokens não é suficiente para prontidão de produção.

O caminho de serving precisou suportar a mesma semântica de requisição que a aplicação:

  • Templates de chat
  • Comportamento de parser de reasoning
  • Parsing de tool calls
  • Prompts de contexto longo
  • Completions curtas
  • Rajadas de requisições no estilo agente

Um modelo pode parecer saudável em testes diretos de completion e ainda falhar sob o perfil real das requisições de produção.

Flags de parser, formatação de requisição e comportamento de tool call fazem parte da história de performance porque determinam se o benchmark consegue rodar. O teste correto não é apenas “o modelo consegue retornar tokens?”. É:

O modelo consegue servir o caminho real de requisição da aplicação corretamente e de forma repetida?

A história de performance mudou depois da reprodução

Os primeiros resultados pareciam espetaculares.

Depois de habilitar a decodificação especulativa, algumas execuções isoladas sugeriram uma melhoria muito maior sobre o baseline não especulativo. Teria dado uma manchete excelente.

Então tentamos reproduzir.

Medições repetidas via chamadas diretas ao runtime, via proxy interno ao cluster e via gateway externo convergiram para baixo.

MediçãoPadrão observadoConfiançaComo usar
Baseline NVFP4 não especulativoFaixa baixa dos 30 tokens/s para gerações curtas isoladasMédia/alta para este setupBaseline útil; não é um número universal de SM120
Execuções iniciais com decodificação especulativaMelhoria aparente muito maiorBaixa como métrica de mancheteManter apenas em nota interna
Resultado reproduzido com decodificação especulativaAproximadamente 2–3x sobre o baseline não especulativoMaiorUsar como a história honesta
Serving concorrente com saídas curtasBaixos milhares de tokens/s agregadosEspecífica ao workloadSinal útil com perfil de produção
Caminho completo de proxy/gatewayPrecisou de validação separadaAlta importânciaObrigatório antes de planejamento de capacidade

A conclusão honesta passou a ser:

A decodificação especulativa foi estável e significativamente mais rápida, mas a melhoria reprodutível ficou mais perto de 2–3x do que do pico inicial.

Esse ainda é um resultado forte. Só não é o número que queríamos inicialmente.

Um pico de curta duração pode ser real sem ser representativo.

Carga com perfil de produção importa mais do que prompts de brinquedo

O benchmark mais útil não foi um prompt de brinquedo.

O workload representativo tinha uma forma parecida com produção:

  • Prompts longos
  • Completions curtas
  • Rajadas de passos de agente concorrentes
  • Classes de requisição mistas
  • Turnos ocasionais de contexto muito longo
  • Tempo realista entre requisições

Reprodução exata não foi possível porque os corpos históricos de prompts e payloads de tool não haviam sido capturados. Em vez disso, o benchmark preservou a forma do workload: timing, alvos de tokens e classe de cenário.

Esse tipo de replay consegue responder perguntas operacionais úteis:

  • O pod de serving permanece estável?
  • Acumula fila?
  • Como o prefill de contexto longo afeta o throughput de decode?
  • O prefix caching ajuda?
  • O caminho do gateway introduz falhas?
  • As configurações de parser são compatíveis com a aplicação?

Não consegue responder perguntas que exigem prompts históricos byte a byte ou payloads de tool exatos.

Sob a fatia de carga testada, o caminho de serving permaneceu estável e não acumulou fila. Requisições de contexto longo continuaram lentas, mas não quebraram o runtime.

Esse é um sinal útil de produção, mesmo não sendo um plano de capacidade definitivo.

Tracing precisa estar ligado antes do benchmark

Uma lição desconfortável foi que algumas execuções históricas não puderam ser reconstruídas com alta fidelidade.

Logs de spend e métricas agregadas foram úteis, mas não foram suficientes. Mostravam timing, contagens de tokens, códigos de status e throughput aproximado. Não recuperavam corpos completos de prompts, payloads de tool ou contexto de trace externo.

Para próximas execuções, tracing precisa ser habilitado antes de o benchmark começar.

Para stacks de proxy compatíveis com OpenAI, isso geralmente significa verificar duas coisas:

environment:
  TRACING_API_KEY: ...

proxy_settings:
  success_callback:
    - tracing_backend

Os nomes exatos variam por proxy e por provedor de tracing, mas o princípio não muda:

Variáveis de ambiente sozinhas podem não habilitar traces. O proxy normalmente precisa de configuração explícita de callback.

Uma vez que o tracing esteja conectado corretamente, próximas execuções de benchmark podem ser reproduzidas e analisadas com muito mais fidelidade.

Lições operacionais do caminho de serving

O runtime de modelo era apenas uma parte da implantação. O restante do caminho de serving expôs várias lições de infraestrutura.

Primeiro, fixe o alvo de hardware durante o benchmark.

É fácil comparar resultados entre diferentes tamanhos de nó de GPU, configurações de memória ou pools de runtime e atribuir a diferença, sem querer, à quantização ou aos kernels. Notas de benchmark devem registrar o alvo de hardware, a linhagem da imagem de runtime, a revisão do modelo, o pacote de kernel e o caminho de roteamento.

Segundo, limites de autoscaler podem falhar fechados.

Se a capacidade de GPU for reduzida zerando limites de recurso via patch, restaurar a capacidade pode exigir remover esses campos em vez de sobrescrevê-los com um objeto vazio. Caso contrário, o autoscaler pode continuar tratando CPU, memória ou GPU como limitados.

Terceiro, um pod de decode saudável não é o mesmo que um caminho de serving externo saudável.

Chamadas diretas ao runtime podem ter sucesso enquanto o gateway retorna erros, porque falta uma rota, um endpoint picker, um registro de serviço ou um mapeamento de modelo. Para prontidão de produção, teste todas as camadas:

  • Endpoint direto do runtime
  • Caminho interno de proxy ou service mesh
  • Caminho de gateway externo

Um modelo não está realmente no ar até que o caminho real de requisição da aplicação funcione.

Checklist prático para serving NVFP4 em SM120

Para times trazendo modelos semelhantes para SM120, este checklist é um bom ponto de partida.

1. Valide a matriz de suporte completa

Não valide apenas a GPU ou apenas o modelo. Valide a combinação:

  • Arquitetura de GPU
  • Versão do CUDA
  • Versão do vLLM
  • Versão do FlashInfer
  • Suporte ao CUTLASS
  • Formato de quantização
  • Dtype do KV cache
  • Modo de decodificação especulativa
  • Comportamento de parser
  • Caminho de gateway/proxy

2. Torne SM120 explícito

Defina configuração do FlashInfer específica para a arquitetura e verifique que a imagem inclui os wheels, cubins e comportamento de cache JIT corretos.

FLASHINFER_CUDA_ARCH_LIST=12.0f
FLASHINFER_FORCE_SM=120f
FLASHINFER_DISABLE_VERSION_CHECK=1

3. Mantenha as configurações de alvo e drafter separadas

O modelo-alvo e o drafter podem diferir em:

  • Precisão
  • Metadados de quantização
  • Layout de checkpoint
  • Variante de arquitetura
  • Suposições de tokenizer
  • Shape de pesos

Trate-os como contratos separados.

4. Faça benchmark em todos os caminhos de serving

Meça performance de runtime direto, performance de proxy interno e performance de gateway externo separadamente. Não assuma que um representa os outros.

5. Reporte números reprodutíveis

Mantenha o pico em notas internas, mas use a medição repetida como manchete.

6. Ligue o tracing antes da execução

Sem traces completos, replay futuro se torna baseado em forma, não em requisição exata.

Resumo final

SM120 não foi uma migração simples, mas se tornou um alvo prático de serving uma vez que o stack foi montado com cuidado.

O formato que funcionou foi:

  • Runtime vLLM recente
  • Suporte ao FlashInfer b12x / SM120
  • Configuração explícita de compute 12.0f / 120f
  • Checkpoint NVFP4 no modelo-alvo via ModelOpt
  • KV cache em FP8
  • Modelo de assistente/draft separado para decodificação especulativa
  • Validação do caminho de produção passando por proxy e gateway

Esse stack produziu uma faixa de serving estável e significativamente mais rápida do que o baseline NVFP4 não especulativo.

O resultado final não foi o número de manchete do início. Foi melhor: um caminho reprodutível e depurável para inferência NVFP4 de modelos grandes em SM120.

Relacionados

Continue lendo

Leituras relacionadas a este tema.