TRL documentation
Destilação Assíncrona
Destilação Assíncrona
Este trainer requer
vllm,transformers>=5.2.0ekernels(o treinamento carrega o FlashAttention do Hub por meio dele). Para treinamento distribuído, apenas o FSDP2 é suportado (o DeepSpeed ZeRO não é).Atualmente,
vllmetransformerspossuem restrições de dependência conflitantes. Para contornar isso, instale o vLLM primeiro, em seguida, force a instalação do transformers e, depois, adicionekernelspor meio do extra do TRL, que fixa a versão que o transformers aceita:pip install vllm pip install 'transformers>=5.2.0' --no-deps pip install 'trl[kernels]'
Visão geral
experimental.async_distillation.AsyncDistillationTrainer é o equivalente assíncrono do DistillationTrainer, com arquitetura semelhante à do experimental.async_grpo.AsyncGRPOTrainer: um worker de rollout em segundo plano gera as próprias conclusões on-policy do aluno e as pontua em relação a um professor, enquanto o treinamento prossegue de forma concorrente, em vez de alternar entre geração e atualizações de gradiente. Diferentemente do trainer síncrono, o professor nunca é carregado localmente — apenas uma URL de servidor vLLM é necessária, de modo que o professor pode ser executado em hardware totalmente separado do aluno e do trainer, ou até mesmo ser um modelo muito maior do que caberia ao lado do aluno.
compute_loss minimiza uma Divergência de Jensen-Shannon generalizada entre as distribuições de tokens por posição do aluno e do professor (beta=0.0 é KL direta, beta=1.0 é KL reversa, valores intermediários interpolam), o mesmo objetivo usado por DistillationTrainer e ServerDistillationTrainer. O treinamento é sempre on-policy: o aluno gera todas as conclusões nas quais ele é treinado.
Destilação on-policy com múltiplos professores (MOPD)
O MOPD não faz parte do artigo 2306.13649, no qual se baseia o objetivo central deste trainer (que descreve um cenário síncrono com um único professor); trata-se de um método separado, descrito em MOPD: Multi-Teacher On-Policy Distillation for Capability Integration in LLM Post-Training. Lá, o MOPD é a terceira de três etapas — SFT geral, em seguida treinamento de RL independente por domínio de um especialista por domínio, e então o MOPD funde esses especialistas congelados em um único aluno. O AsyncDistillationTrainer implementa apenas essa terceira etapa de fusão: os professores especialistas por domínio devem já existir (por exemplo, treinados separadamente com GRPOTrainer/RLOOTrainer) e estar sendo servidos via HTTP antes de você apontar teacher_server_urls para eles. A própria Etapa 3 do artigo usa KL reversa (beta=1.0), e não o padrão deste trainer, beta=0.0 (KL direta).
teacher_server_urls aceita mais de uma entrada. Com uma única entrada, cada amostra é pontuada por aquele único professor (destilação on-policy simples). Com múltiplas entradas, a coluna teacher_id de cada amostra de treinamento seleciona qual professor a pontua — por exemplo, roteando prompts de matemática para um professor especialista em matemática e prompts de código para um professor especialista em código, cada um servido independentemente. Cada amostra é despachada para o seu professor correspondente, nunca sendo média ou ensemble entre professores. Uma amostra com teacher_id ausente ou não mapeado gera um erro em vez de cair silenciosamente para o professor errado. Veja examples/async_distillation_mopd/async_distillation_mopd.py para um exemplo executável que funde os especialistas em matemática, código e seguimento de instruções liberados com Open-MOPD em seu checkpoint SFT compartilhado, e examples/async_distillation_mopd/launcher.py para executá-lo em 4 GPUs com um único comando do Hugging Face Jobs.
Cada professor deve compartilhar o tokenizer do aluno. As conclusões são enviadas aos professores como ids de tokens brutos, e os ids de candidatos que um professor relata de volta indexam diretamente o vocabulário do próprio aluno em
compute_loss. Professores da mesma família de modelos que o aluno (como no exemplo de MOPD, em que um aluno SmolLM3 é fundido a partir de especialistas SmolLM3 treinados a partir do mesmo checkpoint) satisfazem isso; um professor com um vocabulário diferente treina o aluno contra os tokens errados, silenciosamente, a menos que seu vocabulário seja maior que o do aluno.
Checkpoint e retomada: ignore_data_skip tem valor padrão True; o loop de pulo e reprocessamento do Trainer base não se aplica a uma fila de rollout ao vivo. Em vez disso, o índice do primeiro prompt ainda não treinado é salvo em rollout_state.json junto com cada checkpoint e restaurado na retomada, de modo que o worker avança rapidamente até esse prompt sem reprocessar amostras. É a posição treinada, não a do gerador: o worker opera à frente do treinamento pela profundidade da fila de rollout, e essas amostras em buffer são perdidas quando a execução termina, portanto, retomar a partir da posição do gerador pularia prompts que foram gerados, mas nunca treinados. Conjuntos de dados em streaming (IterableDataset) não podem ser reposicionados; seu worker reinicia do prompt 0 na retomada. Com FSDP e bf16=True (o padrão) ou fp16=True, model.safetensors é gravado nesse dtype, mesmo que os pesos mestres sejam fp32, de modo que um checkpoint publicado tem o tamanho do modelo servido, e não o dobro; os pesos mestres fp32 e o estado do otimizador ainda vão para os arquivos de retomada (pytorch_model_fsdp.bin, optimizer.bin), que são os lidos na retomada, portanto, a retomada não é afetada. Sem FSDP, o checkpoint também é o arquivo de retomada, então ele mantém o dtype mestre.
Como difere do DistillationTrainer
Em DistillationTrainer, o modelo professor é carregado localmente: a geração, a passagem direta do professor e a atualização do gradiente ocorrem sequencialmente no mesmo processo. experimental.async_distillation.AsyncDistillationTrainer separa essas responsabilidades da mesma forma que experimental.async_grpo.AsyncGRPOTrainer separa o rollout do GRPO de sua atualização:
- Trabalhador de rollout (processo em segundo plano) — gera conclusões a partir do servidor vLLM do aluno, envia a sequência completa de volta para o
/v1/completionsdo professor resolvido comprompt_logprobspara pontuação forçada pelo professor (sem novos tokens gerados pelo professor) e insere amostras prontas para treinamento em uma fila. - Loop de treinamento (processo principal) — extrai amostras da fila, calcula a perda JSD generalizada e atualiza os pesos do aluno.
Como o professor é pontuado via HTTP em vez de uma passagem local de forward, apenas uma fatia esparsa, top-k, de sua distribuição é transmitida (teacher_top_k), e não o vocabulário completo — consulte a documentação de beta e teacher_top_k em experimental.async_distillation.AsyncDistillationConfig para saber exatamente quais candidatos o protocolo de rede garante um logprob do professor para cada regime de beta.
A cada weight_sync_steps passos de treinamento, os pesos atualizados do aluno são transferidos para seu servidor vLLM via NCCL. Assim como no experimental.async_grpo.AsyncGRPOTrainer, a geração fica à frente do treinamento, portanto as amostras podem refletir uma política ligeiramente desatualizada; max_staleness controla quantas atualizações de pesos uma amostra pode ficar para trás antes de ser descartada.
Início rápido
# train_async_distillation.py
from datasets import load_dataset
from trl.experimental.async_distillation import AsyncDistillationTrainer
dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
trainer = AsyncDistillationTrainer(
model="Qwen/Qwen2.5-0.5B-Instruct",
train_dataset=dataset,
)
trainer.train()O servidor do professor, o servidor vLLM do aluno e o treinador devem ser executados em GPUs separadas. Ambos são instâncias simples de vllm serve, mas precisam de flags diferentes: o professor é apenas pontuado (--logprobs-mode processed_logprobs para que teacher_temperature alcance os logprobs retornados, --max-logprobs -1 para que teacher_top_k possa exceder o limite padrão de 20 do vLLM), enquanto o aluno é apenas gerado e precisa ter a transferência de pesos NCCL habilitada para que o treinador possa enviar os pesos atualizados para ele. A pontuação envia a sequência inteira do aluno e pede ao professor mais um token, portanto o --max-model-len do professor (ou seu padrão) deve ser maior que o do aluno:
# Terminal 1: teacher server on GPU 0 (static, never updated)
CUDA_VISIBLE_DEVICES=0 vllm serve Qwen/Qwen2.5-1.5B-Instruct \
--port 8001 \
--logprobs-mode processed_logprobs \
--max-logprobs -1# Terminal 2: student's vLLM server on GPU 1 (dev mode + NCCL weight transfer are required)
CUDA_VISIBLE_DEVICES=1 VLLM_SERVER_DEV_MODE=1 vllm serve Qwen/Qwen2.5-0.5B-Instruct \
--port 8000 \
--weight-transfer-config '{"backend":"nccl"}'# Terminal 3: training on GPU 2
CUDA_VISIBLE_DEVICES=2 python train_async_distillation.pyLoRA
Passe um peft_config para treinar um adaptador LoRA no aluno em vez do modelo completo:
from peft import LoraConfig
trainer = AsyncDistillationTrainer(
model="Qwen/Qwen2.5-0.5B-Instruct",
train_dataset=dataset,
peft_config=LoraConfig(r=32, lora_alpha=64, target_modules="all-linear"),
)Cada sincronização de pesos precisa enviar o aluno atualizado para o servidor. Por padrão, o trainer mescla o adaptador no modelo base e envia os pesos completos mesclados, o que corresponde a cerca de 15 GB para um modelo de 7B em cada sincronização. Inicie o servidor vLLM do aluno com --enable-lora e ele enviará apenas o adaptador, cerca de 160 MB em r=32:
CUDA_VISIBLE_DEVICES=1 VLLM_SERVER_DEV_MODE=1 VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 vllm serve Qwen/Qwen2.5-0.5B-Instruct \
--port 8000 \
--weight-transfer-config '{"backend":"nccl"}' \
--enable-lora \
--max-lora-rank 32 \
--max-loras 6VLLM_ALLOW_RUNTIME_LORA_UPDATING=1 expõe o endpoint para o qual o trainer envia cada novo adapter. Adicione VLLM_WORKER_MULTIPROC_METHOD=spawn se você estiver servindo com tensor-parallel e mantenha --weight-transfer-config em qualquer caso: o trainer só escolhe um modo de sincronização quando inicia, momento em que o servidor já está ativo, e a sincronização mesclada é o fallback. O servidor teacher não é afetado: ele nunca é atualizado.
--max-lora-rank deve ser um dos valores 1, 8, 16, 32, 64, 128, 256, 320, 512. Ele define o rank máximo que o servidor pode atender, e não o rank que ele efetivamente servirá, portanto um adaptador r=4 funciona perfeitamente sob 8. --max-loras deve ser pelo menos max_staleness + 2: o trainer mantém max_staleness + 1 versões de adaptador registradas para que um rollout iniciado sob uma política mais antiga possa ser concluído sob ela, em vez de trocar de política no meio da geração, e cada sincronização carrega a próxima versão antes de descarregar a mais antiga. O trainer verifica ambos os valores ao iniciar e informa com quais valores reiniciar o servidor.
O vLLM carrega adaptadores do disco em vez da rede, portanto o trainer grava cada versão em <output_dir>/.vllm_lora/ e fornece o caminho ao servidor. Se o servidor estiver em uma máquina diferente, esse diretório precisa estar em um sistema de arquivos visível para ambos.
<output_dir>/.vllm_lora/é um cache de serviço, não o local onde o seu adaptador treinado é armazenado. Ele apenas mantém as versões mais recentes, e cada uma é excluída pouco depois de sair da janela de obsolescência. Os checkpoints são o que preservam o adaptador que você treinou, portanto, não os desative: comsave_strategy="no", uma execução de várias horas termina e não deixa nada para trás. Ooutput_dirtambém precisa ser legível pelo servidor e precisa sobreviver ao término do job, portanto, um armazenamento temporário local do nó não funcionará, mesmo que isso mantivesse as sincronizações fora de um sistema de arquivos compartilhado lento.
A sincronização de adaptadores nem sempre é mais rápida. O vLLM gera tokens aproximadamente 1,33 a 1,39 vezes mais devagar ao servir um adaptador LoRA do que ao servir pesos mesclados, e a geração geralmente é o que domina o tempo real de execução. Em contrapartida, você obtém uma pausa muito mais curta em cada sincronização e cerca de 1% da largura de banda de sincronização. Se o throughput de geração for o seu gargalo, a sincronização mesclada ainda costuma ser a melhor escolha.
Métricas registradas
Um rollout passa por várias etapas antes de se tornar um gradiente. Primeiro, vamos dar uma olhada nas diferentes etapas, de um prompt para a composição final do nosso lote:
Dataset row = PROMPT (message list + optional `teacher_id`)
└─ ROLLOUT (1 prompt -> 1 student completion -> 1 teacher scoring call)
├─ generate (student's vLLM /v1/completions, sampled)
└─ score (the routed teacher's /v1/completions with `prompt_logprobs`, teacher-forced)
└─ SAMPLE (prompt + completion + the teacher's sparse per-position distribution)
════════════ Trainer process boundary: `rollout_buffer` (mp.Queue) ════════════
└─ SAMPLE (Pulled 1 at a time; dropped if staleness > `max_staleness`)
└─ ROW (Planner assigns it to one of `dp` rows, Σ Lᵢ²-balanced)
└─ MICRO-BATCH (`dp` rows, one per rank)
└─ PACKED ROW (1 concat sequence, `position_ids` reset per sample)
└─ FORWARD (`compute_loss`, bs=1, inter-rank padding stripped)
└─ OPTIMIZER STEP (from `grad_accum` micro-batches)Lendo de cima para baixo:
- um rollout é um prompt gerado uma vez e pontuado uma vez. Não há grupo: a destilação não possui uma linha de base de vantagem para calcular entre gerações, portanto, um prompt não é repetido e um rollout gera exatamente uma amostra de treinamento.
- um sample é aquela conclusão mais os
teacher_top_kprincipais candidatos do professor para cada posição da conclusão. É isso que atravessa a fronteira do processo. - além do limite, o trainer planeja as amostras em linhas (uma por rank de DP), empacota cada linha em uma única sequência concatenada e acumula
grad_accummicro-lotes em um único passo do otimizador.
Portanto, dividimos nossas métricas com base na etapa (ou entidade) sobre a qual elas se referem:
| namespace | entidade que ele conta |
|---|---|
rollout/ | um ciclo de geração e pontuação: quanto tempo levou, até onde chegou |
completions/ | o que o aluno (vLLM) gerou para um prompt |
sample/ | representa uma amostra de treinamento, conforme ela chega à fila |
batch/ | um micro-lote ou um passo do otimizador (construído a partir de uma ou várias amostras) |
perf/ | segundos e FLOPs medidos |
Tudo o que a própria perda mede é registrado sem prefixo (jsd, entropy, teacher_entropy), com uma divisão teacher_*/<teacher_id> sob MOPD — veja a divergência abaixo.
Vamos também definir alguns termos para tornar as métricas de tokens inequívocas:
- Os tokens gerados são aqueles produzidos pelo aluno (
completions/*). - Os tokens forwarded são todos os tokens que a passagem forward processa: o prompt e os tokens gerados.
- Os tokens treinados são o subconjunto sobre o qual a perda é calculada, onde
completion_mask == 1. Treinado ≠ gerado: uma posição de conclusão para a qual o professor não pontuou nenhum candidato é mascarada na divergência, mas ainda assim é encaminhada.
Como os números de lote se compõem
Uma linha é o que um rank de DP faz o forward em um micro-lote: várias amostras concatenadas em uma única sequência, com position_ids reiniciando em cada fronteira de amostra. O planejador decide em qual linha cada amostra será alocada (equilibrando Σ Lᵢ² para que nenhum rank fique para trás), e um data collator realiza a concatenação.
Em todas as nossas métricas, um “step” sempre significa um passo COMPLETO do otimizador, nunca um micro-lote. gradient_accumulation_steps micro-lotes formam um passo, e toda métrica chamada _per_step é por passo do otimizador, correspondendo a global_step, logging_steps e ao restante do vocabulário do Trainer. Quando uma métrica é por micro-lote, isso é indicado (batch/microbatches_per_step) ou ela é uma quantidade por linha (batch/row_*, batch/samples_per_row).
Portanto, uma etapa contém um número fixo de row-slots:
row-slots per step = gradient_accumulation_steps x world_sizeo que faz com que as métricas do lote se componham em uma escada que você pode verificar entre si:
sample sample/forwarded_tokens_mean
└─ packed into a ROW batch/samples_per_row, batch/row_tokens_mean
└─ one row per DP rank = one micro-batch
└─ gradient_accumulation_steps micro-batches = one STEP
batch/samples_per_step, batch/forwarded_tokens_per_stepPortanto, batch/samples_per_step ≈ row-slots x batch/samples_per_row, e o mesmo vale para tokens. Os dois lados só coincidem até a variação entre micro-lotes dentro do passo — as métricas por passo são somas, enquanto as por linha são médias —, então espere uma diferença de fração de um percentual, não uma correspondência exata.
batch/row_fill_frac é o que deve ser monitorado quando as amostras são longas: uma amostra de 10k tokens preenche mal um orçamento de 32k (três cabem, quatro nunca cabem, então o empacotador frequentemente obtém apenas duas e a linha fica ~77% cheia), enquanto amostras de 1k token preenchem-no quase perfeitamente. Isso é quantização, não um bug, e token_budget é o parâmetro de ajuste — tendo em mente que a atenção é O(L²) por sequência, de modo que uma linha mais cheia de sequências longas não custa linearmente mais memória.
A fila de rollout
O worker envia amostras pontuadas para uma fila e o trainer as retira dela. Quatro métricas descrevem essa única fila e respondem a perguntas diferentes:
| métrica | pergunta |
|---|---|
sample/rollout_queue_size | quantas amostras estão aguardando no momento |
sample/time_in_queue_s | por quanto tempo uma única amostra ficou ali antes de ser usada no treinamento — a metade em segundos de sua natureza off-policy |
perf/rollout_wait_s | por quanto tempo o treinamento ficou bloqueado porque a fila estava vazia |
rollout/backpressure_s | por quanto tempo a geração (o worker) ficou bloqueada porque a fila estava cheia |
As duas últimas métricas (perf/rollout_wait_s e rollout/backpressure_s) são imagens espelhadas e nunca são grandes ao mesmo tempo! Lidas em conjunto com o tamanho da fila, elas indicam qual lado é o gargalo:
- fila quase vazia,
perf/rollout_wait_salto → limitado pela geração. O treinador está com falta de dados; verifiquerollout/generated_tok_s,rollout/concurrencyerollout/score_s. - fila quase cheia,
rollout/backpressure_salto → limitado pelo trainer. A geração está sendo limitada e sua saída está envelhecendo na fila, então observesample/staleness_meansubir. - ambos próximos de zero → equilibrado!
Completions
Uma completion é o que o servidor vLLM do aluno gerou para um prompt.
| métrica | significado |
|---|---|
completions/mean_length | tokens gerados por rollout |
completions/min_length, completions/max_length | conclusão mais curta e mais longa na janela |
completions/clipped_ratio | fração de conclusões que não terminaram em EOS, ou seja, foram cortadas por max_completion_length |
Rollouts
Um rollout é um prompt levado até o fim: gerado pelo aluno e, em seguida, processado pelo professor com forçamento de professor. Como a chamada ao professor está no caminho crítico de cada rollout (diferentemente do GRPO, em que a pontuação é um loop separado sobre grupos concluídos), um professor lento aparece diretamente como rollout/duration_s.
| métrica | significado |
|---|---|
rollout/duration_s | tempo real para um rollout, do despacho até uma amostra pontuada: geração e a chamada ao professor |
rollout/score_s | dessa, o tempo que a chamada /v1/completions do professor levou |
teacher_score_s/<id> | Apenas MOPD: o mesmo, por professor. Um especialista lento limita apenas os rollouts roteados para ele, o que a média misturada oculta |
rollout/generated_tok_s | vazão de geração no último intervalo (janela deslizante), de modo que uma parada fica visível |
rollout/concurrency | rollouts em andamento, ou seja, sendo gerados ou pontuados |
rollout/vllm_retry_total | solicitações de vLLM repetidas, seja para o servidor do aluno ou para o de um professor. Um servidor degradado, caso contrário, se manifesta como lentidão inexplicada. Este item está aqui, em vez de em completions/, porque ele contabiliza solicitações a um servidor, e não o texto gerado |
rollout/backpressure_s | por quanto tempo a geração foi bloqueada porque a fila de rollout estava cheia. Veja a fila de rollout |
Amostras chegando da fila
Cada amostra é um RolloutSample: um prompt, a conclusão do aluno e a pontuação esparsa do professor para ela. O worker as insere em rollout_buffer; tudo abaixo é medido no lado do trainer, conforme ele as extrai para o treinamento.
| métrica | significado |
|---|---|
sample/forwarded_tokens_mean, sample/forwarded_tokens_max | tokens em uma amostra: prompt + gerado. O empacotamento é uma questão de nível de linha, veja batch/row_* |
sample/trained_tokens_mean | dessas, quantas são consideradas no cálculo da perda |
sample/rollout_queue_size | amostras pontuadas aguardando na fila |
sample/time_in_queue_s | por quanto tempo esta amostra ficou nessa fila antes de ser usada no treinamento. Não é o mesmo que perf/rollout_wait_s — veja a fila de rollout |
sample/staleness_mean, sample/staleness_max | quantas versões de política estão atrás dos dados. jsd mostra o efeito da off-policyness na perda; isso mostra a causa |
sample/dropped_stale_total | amostras descartadas por exceder max_staleness |
Lotes
Um micro-lote é world_size linhas (lembre-se de que o packing aplanar em 1 sequência), então um por rank de DP, de modo que o rank i faz o forward da linha i. gradient_accumulation_steps desses formam um passo do otimizador. Um passo então cobre gradient_accumulation_steps × world_size linhas no total. Cada métrica _per_step abaixo é uma soma sobre o passo inteiro e entre todos os ranks; as métricas batch/row_* são médias sobre as linhas.
| métrica | significado |
|---|---|
batch/forwarded_tokens_per_step, batch/trained_tokens_per_step | tokens em um passo do otimizador, encaminhados e treinados, respectivamente |
batch/samples_per_step | amostras de treinamento por passo do otimizador |
batch/microbatches_per_step | contados, não lidos da configuração |
batch/masked_token_frac | tokens encaminhados com completion_mask == 0 — a parte do encaminhamento que não gera gradiente |
batch/samples_per_row, batch/row_tokens_mean, batch/row_tokens_max | o quão densamente o planejador empacotou a linha de cada rank |
batch/row_fill_frac | tokens da linha em relação a token_budget. Valores baixos indicam que o orçamento não está sendo utilizado |
batch/row_imbalance | max Σ Lᵢ² / mean Σ Lᵢ² entre as linhas. A atenção é O(L²), então isso prevê qual rank atrasa o all-reduce de gradientes. 1.0 é perfeito |
batch/pad_frac | padding entre ranks. Custa apenas bytes de broadcast; é removido antes do forward |
batch/dropped_oversize_total | amostras descartadas por exceder token_budget |
A divergência
O que a própria métrica de objetivo mede, em média, sobre os tokens treinados da janela. Estes são os números a serem observados para o aprendizado, em oposição aos de throughput.
| métrica | significado |
|---|---|
jsd | a JSD generalizada que a perda minimiza, no beta configurado. Uma queda indica que a distribuição do aluno está convergindo para a do professor |
entropy | a própria entropia preditiva do aluno. Um colapso aqui com uma queda em jsd indica que o aluno está estreitando, em vez de aprender |
teacher_entropy | a entropia do professor sobre os candidatos que ele reportou. Limitada inferiormente ao valor verdadeiro, pois apenas teacher_top_k candidatos cruzam a rede |
teacher_jsd/<id> | Apenas MOPD: jsd restrito aos tokens pontuados pelo professor. Professores em diferentes domínios podem divergir em taxas muito diferentes, o que o jsd misturado confunde |
teacher_entropy/<id> | Apenas MOPD: a mesma decomposição de teacher_entropy |
teacher_token_frac/<id> | Apenas MOPD: a proporção de tokens pontuados que o professor assumiu. O desvio de roteamento é invisível de outra forma: um professor com falta de linhas ainda relata um teacher_jsd/<id> saudável |
Não existe entropy por professor: a entropia do aluno é uma propriedade da própria política dele, não de qual professor pontuou a amostra, então a métrica combinada já a cobre.
Desempenho
Throughput mede quantos tokens o trainer processa por segundo. Model FLOPs Utilization (MFU) estima a porcentagem da capacidade teórica de computação dos dispositivos de treinamento utilizada para as passadas de forward e backward do modelo. A capacidade utilizada para essa estimativa depende do hardware e da precisão do treinamento.
O throughput e o MFU são reportados em duas bases de tempo:
_fwd_bwdmede o desempenho durante as passadas de forward e backward._wall_clockmede o desempenho ao longo da etapa completa do otimizador, incluindo as atualizações do otimizador, a sincronização de pesos e o tempo gasto aguardando os rollouts.
Essa diferença ajuda a distinguir o desempenho de computação do custo de espera e coordenação. Ambas as métricas de MFU descrevem apenas os dispositivos de treinamento: elas não incluem a computação ou a capacidade de hardware de servidores externos de rollout ou de teacher. A MFU de tempo real reflete o tempo que o treinador gasta aguardando esses serviços, e não a própria utilização desses serviços.
O MFU é reportado apenas quando a capacidade de computação de pico é conhecida para o dispositivo de treinamento local na precisão selecionada. Caso contrário, o MFU é omitido; o treinamento continua e as métricas de throughput e tempo permanecem disponíveis.
| métrica | significado |
|---|---|
perf/step_s | tempo real entre as etapas do otimizador: inclui cálculos, otimizador, sincronização de pesos e esperas na fila |
perf/fwd_bwd_s | forward + backward, somado sobre os micro-lotes do passo. Este é o denominador de todas as métricas _fwd_bwd abaixo |
perf/fwd_s | a parte de forward dele. fwd_s / fwd_bwd_s próximo de 1/3 é a divisão usual; valores maiores indicam que o backward é barato ou que o recompute está sendo pago no forward |
perf/optimizer_s | optimizer.step() |
perf/rollout_wait_s | por quanto tempo o trainer ficou bloqueado porque a fila estava vazia. Veja a fila de rollout |
perf/weight_sync_s | um sincronização completa, além de _pause_s (aguardando o vLLM), _barrier_s (desvio de rank) e _transfer_s (os bytes) |
perf/forwarded_tok_s_fwd_bwd, perf/forwarded_tok_s_wall_clock | tokens encaminhados por segundo em cada base |
perf/trained_tok_s_wall_clock | o mesmo, contando apenas os tokens que a perda viu |
perf/mfu_fwd_bwd, perf/mfu_wall_clock | utilização de FLOPs do modelo em cada base |
Filosofia de design
Este trainer é mantido intencionalmente minimalista e não foi projetado para se tornar uma solução de uso geral. Se você precisar de um recurso que não é suportado, recomendamos clonar o repositório e adaptar o trainer diretamente às suas necessidades. Novos recursos só serão considerados quando houver demanda significativa da comunidade.
AsyncDistillationConfig
classe trl.experimental.async_distillation.AsyncDistillationConfig
< source >( output_dir: str | None = Noneper_device_train_batch_size: int = 8num_train_epochs: float = 3.0max_steps: int = -1learning_rate: float = 1e-06lr_scheduler_type: transformers.trainer_utils.SchedulerType | str = 'linear'lr_scheduler_kwargs: dict | str | None = Nonewarmup_steps: float = 0optim: transformers.training_args.OptimizerNames | str = 'adamw_torch_fused'optim_args: str | None = Noneweight_decay: float = 0.0adam_beta1: float = 0.9adam_beta2: float = 0.999adam_epsilon: float = 1e-08optim_target_modules: None | str | list[str] = Nonegradient_accumulation_steps: int = 1average_tokens_across_devices: bool = Truemax_grad_norm: float = 1.0label_smoothing_factor: float = 0.0bf16: bool | None = Nonefp16: bool = Falsebf16_full_eval: bool = Falsefp16_full_eval: bool = Falsetf32: bool | None = Nonegradient_checkpointing: bool = Truegradient_checkpointing_kwargs: dict[str, typing.Any] | str | None = Nonetorch_compile: bool = Falsetorch_compile_backend: str | None = Nonetorch_compile_mode: str | None = Noneuse_liger_kernel: bool = Falseliger_kernel_config: dict[str, bool] | None = Noneuse_cache: bool = Falseneftune_noise_alpha: float | None = Nonetorch_empty_cache_steps: int | None = Noneauto_find_batch_size: bool = Falselogging_strategy: transformers.trainer_utils.IntervalStrategy | str = 'steps'logging_steps: float = 1logging_first_step: bool = Falselog_on_each_node: bool = Truelogging_nan_inf_filter: bool = Trueinclude_num_input_tokens_seen: str | bool = 'no'log_level: str = 'passive'log_level_replica: str = 'warning'disable_tqdm: bool | None = Nonereport_to: None | str | list[str] = 'none'run_name: str | None = Noneproject: str = 'huggingface'trackio_space_id: str | None = Nonetrackio_bucket_id: str | None = Nonetrackio_static_space_id: typing.Union[str, NoneType, typing.Literal[False]] = Noneeval_strategy: transformers.trainer_utils.IntervalStrategy | str = 'no'eval_steps: float | None = Noneeval_delay: float = 0per_device_eval_batch_size: int = 8prediction_loss_only: bool = Falseeval_on_start: bool = Falseeval_do_concat_batches: bool = Trueeval_use_gather_object: bool = Falseeval_accumulation_steps: int | None = Noneinclude_for_metrics: list[str] = <factory>batch_eval_metrics: bool = Falsesave_only_model: bool = Falsesave_strategy: transformers.trainer_utils.SaveStrategy | str = 'steps'save_steps: float = 500save_on_each_node: bool = Falsesave_total_limit: int | None = Noneenable_jit_checkpoint: bool = Falsepush_to_hub: bool = Falsehub_token: str | None = Nonehub_private_repo: bool | None = Nonehub_model_id: str | None = Nonehub_strategy: transformers.trainer_utils.HubStrategy | str = 'every_save'hub_always_push: bool = Falsehub_revision: str | None = Noneload_best_model_at_end: bool = Falsemetric_for_best_model: str | None = Nonegreater_is_better: bool | None = Noneignore_data_skip: bool = Truerestore_callback_states_from_checkpoint: bool = Falsefull_determinism: bool = Falseseed: int = 42data_seed: int | None = Noneuse_cpu: bool = Falseaccelerator_config: dict | str | None = Noneparallelism_config: accelerate.parallelism_config.ParallelismConfig | None = Nonedataloader_drop_last: bool = Falsedataloader_num_workers: int = 0dataloader_pin_memory: bool = Truedataloader_persistent_workers: bool = Falsedataloader_prefetch_factor: int | None = Nonedataloader_multiprocessing_context: str | None = Nonedataloader_in_order: bool = Trueremove_unused_columns: bool = Truelabel_names: list[str] | None = Nonetrain_sampling_strategy: str = 'random'length_column_name: str = 'length'ddp_find_unused_parameters: bool | None = Noneddp_bucket_cap_mb: int | None = Noneddp_broadcast_buffers: bool | None = Noneddp_static_graph: bool | None = Noneddp_backend: str | None = Noneddp_timeout: int = 1800fsdp: str | None = Nonefsdp_config: dict[str, typing.Any] | str | None = Nonedeepspeed: dict | str | None = Nonedebug: str | list[transformers.debug_utils.DebugOption] = ''skip_memory_metrics: bool = Truedo_train: bool = Falsedo_eval: bool = Falsedo_predict: bool = Falseresume_from_checkpoint: str | None = Nonelocal_rank: int = -1model_init_kwargs: dict[str, typing.Any] | str | None = Nonedtype: str = 'float32'trust_remote_code: bool = Falserouter_aux_loss_coef: float | None = Nonemax_completion_length: int = 2048temperature: float = 1.0top_p: float = 1.0top_k: int = 0min_p: float | None = Nonerepetition_penalty: float = 1.0chat_template_kwargs: dict | str | None = Nonevllm_server_base_url: str = 'http://localhost:8000'vllm_server_timeout: float = 1800.0teacher_server_urls: dict[str, str] | str | None = Nonebeta: float = 0.0teacher_temperature: float = 1.0teacher_top_k: int = 8add_tail_bucket: bool = Truetoken_budget: int | None = Nonemax_concurrent_rollouts: int | None = Nonemax_staleness: int = 4queue_maxsize: int = 1024weight_sync_steps: int = 1rollout_worker_timeout: float = 300.0log_completions: bool = Falselog_completions_steps: int = 100num_completions_to_print: int | None = None )
Parâmetros que controlam o modelo
- model_init_kwargs (
dict[str, Any]oustr, opcional) — Argumentos de palavra-chave para from_pretrained, usados ao instanciar o modelo aluno a partir de um caminho. O valor derevisiontambém é usado ao carregar a classe de processamento. - dtype (
str, opcional, padrão é"float32") — Tipo de dados para carregar o modelo student, um dos"auto","bfloat16","float16"ou"float32". O padrão é"float32"porque a divergência entre treinamento e inferência, contra a qual os treinadores assíncronos são medidos (Defeating the Training-Inference Mismatch via FP16, explicada para eles em Defeating the trainer-generator precision mismatch in TRL) ) é sensível à própria precisão do treinador. Fechar essa lacuna de ponta a ponta também exige servir o servidor vLLM do student no mesmo dtype (vllm serve --dtype). Um dtypevllm serve --dtype). Adtypeemmodel_init_kwargstem precedência. Os servidores de teacher não são afetados: eles nunca são atualizados.Isso apenas define o dtype em que os pesos são carregados. Sob FSDP com precisão mista (
bf16=Trueé o padrão), o accelerate faz o upcast de todos os parâmetros treináveis para fp32 antes do sharding, de modo que os pesos mestres e o estado do otimizador ficam em fp32, independentemente do quedtypeindique, enquanto o forward e o backward são executados em bf16 ou fp16. Os checkpoints são então gravados nesse dtype de precisão mista, e não em fp32. Sem FSDP, nada faz upcast:dtype="bfloat16"combf16=Truemantém pesos e estados do otimizador em bf16, o que corresponde a treinamento puro em bf16. - trust_remote_code (
bool, opcional, padrãoFalse) — Se permite carregar modelos e tokenizers que incluem código Python personalizado do Hub. Encaminhado para from_pretrained e from_pretrained. - router_aux_loss_coef (
float, opcional) — Coeficiente da perda auxiliar de balanceamento de carga para modelos Mixture-of-Experts (MoE), adicionada à perda de treinamento com esse peso. Quando não definido, o valor declarado na configuração do modelo é utilizado. Defina como0.0para desativá-la. Falha quando usado com um modelo não-MoE.
Parâmetros que controlam a geração
- max_completion_length (
int, opcional, padrão2048) — Número máximo de tokens a serem gerados por conclusão. - temperature (
float, opcional, padrão1.0) — Temperatura para amostragem das conclusões on-policy do aluno. - top_p (
float, opcional, padrão1.0) — Parâmetro de amostragem Top-p (núcleo) para geração on-policy. - top_k (
int, opcional, padrão0) — Parâmetro de amostragem top-k para geração on-policy.0desativa o filtro top-k. - min_p (
float, opcional) — Probabilidade mínima do token, que será escalada pela probabilidade do token mais provável. Deve ser um valor entre0.0e1.0. Valores típicos estão na faixa de0.01-0.2. - repetition_penalty (
float, opcional, padrão1.0) — Float que penaliza novos tokens com base em sua presença no prompt e no texto gerado até o momento. Valores >1.0incentivam o modelo a usar novos tokens, enquanto valores <1.0incentivam o modelo a repetir tokens. - chat_template_kwargs (
dict[str, Any], opcional) — Argumentos de palavra-chave adicionais para passar à funçãoapply_chat_templateao gerar completions.
Parâmetros que controlam os servidores vLLM
- vllm_server_base_url (
str, opcional, padrão"http --//localhost:8000"): URL base do servidor vLLM do aluno, usada tanto para geração quanto para o streaming de atualizações de pesos. - vllm_server_timeout (
float, opcional, padrão1800.0) — Tempo limite, em segundos, para chamadas aos servidores vLLM: aguardar o servidor do aluno ficar pronto na inicialização, cada solicitação de geração ou pontuação e cada chamada de sincronização de pesos. Aguardar o servidor e as chamadas de sincronização de pesos geram erro quando o tempo limite é excedido; uma solicitação de geração ou pontuação que exceder o tempo limite é reprocessada. - teacher_server_urls (
dict[str, str], opcional, padrão{"default" -- "http://localhost:8001"}): Servidor(es) vLLM do teacher, cada um iniciado comvllm serve <teacher-model> --logprobs-mode processed_logprobs --max-logprobs -1. Cada teacher é estático: este trainer nunca transmite atualizações de pesos para nenhum deles, portanto um teacher não precisa deVLLM_SERVER_DEV_MODEnem de um backend de transferência de pesos, apenas dessas duas flags, que tornam sua pontuação exata (vejateacher_temperatureeteacher_top_k). Todos os teachers devem compartilhar o tokenizer do student: as conclusões são enviadas como ids de tokens brutos, e os ids que um teacher reporta de volta são usados diretamente para indexar o próprio vocabulário do student emcompute_loss. Um teacher com um vocabulário diferente treina contra os tokens errados, silenciosamente se seu vocabulário não for maior que o do student. A pontuação é uma solicitação teacher-forced contra os próprios tokens de conclusão do student (max_tokens=1,prompt_logprobs=teacher_top_k,temperature=teacher_temperature), a mesma solicitação queget_sequence_logprobs()emite para os trainers de teacher-servidor síncronos. Devido a esse token descartável, um teacher precisa de um--max-model-lenmaior que o do student, ou as conclusões mais longas não podem ser pontuadas. Uma única entrada pontua todas as amostras (destilação on-policy de teacher único simples, o padrão). Múltiplas entradas habilitam MOPD (destilação on-policy de múltiplos teachers, veja MOPD): a colunateacher_idde cada linha de treinamento seleciona qual entrada a pontua, por exemplo{"math": "http://localhost:8002", "code": "http://localhost:8003"}com umteacher_idde"math"ou"code"por linha.
Parâmetros que controlam a perda de destilação
- beta (
float, opcional, padrão0.0) — Coeficiente de interpolação para a Divergência Generalizada de Jensen-Shannon.0.0é KL forward (mean-seeking),1.0é KL reverse (mode-seeking), e valores intermediários interpolam, seguindo a mesma formulação de JSD generalizada que o DistillationTrainer calcula internamente. O suporte sobre o qual a divergência é calculada varia conforme o regime, espelhando oServerDistillationTrainer: embeta=0.0, é utilizado o suporte completo reportado pelo teacher, de largurateacher_top_k(mais o bucket de cauda), pois o peso do KL forward é exatamente o que esse suporte fornece. Embeta != 0.0, o suporte é reduzido para apenas dois candidatos — o token top-1 do próprio teacher e o token real/realizado da completion — pois esses são os únicos dois tokens para os quais o protocolo de rede garante um logprob do teacher sem transmitir um vocabulário mais amplo (ou completo); qualquer coisa mais ampla seria apenas uma aproximação probabilística de cobrir os tokens prováveis do próprio student, não uma garantia. - teacher_temperature (
float, opcional, padrão1.0) — Temperatura do softmax da divergência, aplicada aos dois lados: enviada ao teacher para que o vLLM calcule seus logprobs nessa temperatura no servidor (exato, não um reescalonamento no cliente) e aplicada aos próprios logits do student emcompute_loss, espelhando a únicatemperaturedoServerDistillationTrainer. Não tem relação comtemperature, que controla apenas como o student amostra suas conclusões. O servidor do teacher deve ser executado com--logprobs-mode processed_logprobspara que isso afete os logprobs retornados; sem isso, o teacher relata silenciosamente logprobs brutos e essa configuração afeta apenas o lado do student. - teacher_top_k (
int, opcional, padrão8) — Número de tokens candidatos por posição solicitados ao teacher viaprompt_logprobs. Apenas esses candidatos (além do token realizado, que o vLLM sempre relata mesmo quando está fora do top-k, além de um bucket de cauda que captura a massa de probabilidade restante, vejaadd_tail_bucket) são usados para aproximar a distribuição do teacher; o vocabulário completo nunca é transmitido via HTTP. O lado do student da divergência é exato (calculado localmente, não aproximado), pois o student é o modelo em treinamento e seus logits completos já estão disponíveis emcompute_loss.8é um padrão leve para testes de fumaça; configurações de destilação on-policy em produção em frameworks de RL adjacentes usam um suporte esparsos do teacher em aproximadamente a mesma faixa (miles usa16por padrão, EasyOPD usa64), portanto aumentar esse valor para16–64é razoável quando o treinamento for de fato iniciado. Qualquer valor acima de20exige que o servidor do teacher tenha sido iniciado com--max-logprobs -1, o que remove o limite padrão de logprob por token do vLLM. - add_tail_bucket (
bool, opcional, padrãoTrue) — Se deve adicionar um bucket de cauda representando a massa de probabilidade restante fora deteacher_top_k, para evitar uma divergência trivialmente pequena quandoteacher_top_ké pequeno. - token_budget (
int, opcional) — Número máximo de tokens reais empacotados em uma única linha (um forward de um rank DP). UmTokenBudgetBatcherforma micro-lotes equilibrados por Σ Lᵢ² cujas linhas permanecem dentro desse orçamento, limitando a memória de pico independentemente da contagem de amostras (o número de amostras por linha se torna dinâmico). SeNone(padrão), é definido como omax_model_lendo servidor vLLM do aluno (consultado no início do treinamento) — o limite para o comprimento de prompt + completude — de modo que nenhuma amostra de rollout possa exceder o orçamento. Uma amostra mais longa quetoken_budgetnão cabe em nenhuma linha e é descartada com um aviso, contabilizada comobatch/dropped_oversize_total.
Parâmetros que controlam o pipeline de rollout assíncrono
- max_concurrent_rollouts (
int, opcional) — Número máximo de rollouts em andamento simultaneamente nos servidores vLLM do aluno e dos professores, seja gerando ou sendo pontuados. SeNone, é definido comomax(max_staleness, 1) * per_device_train_batch_size * gradient_accumulation_steps * num_processes. - max_staleness (
int, opcional, padrão4) — Número máximo de etapas de atualização de pesos que uma amostra de rollout pode ficar atrás da versão atual do modelo antes de ser descartada. - queue_maxsize (
int, opcional, padrão1024) — Número máximo de amostras de rollout a serem armazenadas em buffer na fila de rollout. - weight_sync_steps (
int, opcional, padrão1) — Número de etapas de treinamento entre as sincronizações de pesos para o servidor vLLM do aluno. - rollout_worker_timeout (
float, opcional, padrão300.0) — Segundos desde o último heartbeat do worker de rollout após os quais o trainer o considera travado e interrompe a execução.
Parâmetros que controlam o registro de logs
- log_completions (
bool, opcional, padrãoFalse) — Se deve registrar uma amostra de pares (prompt, completion) a cadalog_completions_stepsamostras pontuadas. - log_completions_steps (
int, opcional, padrão100) — Número de amostras pontuadas entre os registros de completions. Usado apenas selog_completionsforTrue. Contado em amostras que o worker de rollout pontuou, não em passos do otimizador: o worker roda em um processo separado do trainer e não tem visibilidade sobreself.state.global_step. - num_completions_to_print (
int, opcional) — Número de conclusões a serem exibidas comrich. SeNone, todas as conclusões são registradas.
Classe de configuração para o experimental.async_distillation.AsyncDistillationTrainer.
Esta classe inclui apenas os parâmetros específicos para a destilação on-policy assíncrona. Para a lista completa de argumentos de treinamento, consulte a documentação de TrainingArguments. Observe que os valores padrão nesta classe podem diferir daqueles em TrainingArguments. Sua estrutura espelha a de AsyncGRPOConfig (pipeline assíncrono, servidor vLLM e campos de log são os mesmos), com os campos de clipping/grupo do GRPO substituídos pelos campos de perda de destilação do professor abaixo.
Esses parâmetros possuem valores padrão diferentes de TrainingArguments:
logging_steps: O valor padrão é1em vez de500.gradient_checkpointing: O padrão éTrueem vez deFalse.bf16: Padrão éTruesefp16não estiver definido, em vez deFalse.learning_rate: O valor padrão é1e-6em vez de5e-5.ignore_data_skip: O valor padrão éTrueem vez deFalse; o loop de skip-and-replay do Trainer base não se aplica à fila de rollout assíncrono.
AsyncDistillationTrainer
classe trl.experimental.async_distillation.AsyncDistillationTrainer
< source >( model: str | PreTrainedModelargs: trl.experimental.async_distillation.async_distillation_config.AsyncDistillationConfig | None = Nonetrain_dataset: datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset | None = Noneprocessing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | None = Nonecallbacks: list[transformers.trainer_callback.TrainerCallback] | None = Noneoptimizers: tuple[typing.Optional[torch.optim.Optimizer], typing.Optional[torch.optim.lr_scheduler.LambdaLR]] = (None, None)peft_config: PeftConfig | None = None )
Parâmetros
- model (
strou PreTrainedModel) — Modelo de aluno a ser treinado. Pode ser:- Uma string, sendo o model id de um modelo pré-treinado hospedado em um repositório de modelos no huggingface.co, ou um caminho para um directory contendo pesos de modelo salvos usando save_pretrained. Carregado com from_pretrained.
- Um objeto PreTrainedModel. Sua implementação de atenção é alterada para o backend FlashAttention do trainer para treinamento sem padding.
O nome do modelo na configuração é usado para identificar o modelo aluno em seu servidor vLLM.
- args (experimental.async_distillation.AsyncDistillationConfig, opcional) —
Configuração para este trainer. Se
None, uma configuração padrão é usada. - train_dataset (Dataset ou IterableDataset) —
Dataset a ser usado para o treinamento. Deve incluir uma coluna
"prompt"(formato conversacional). Colunas adicionais são ignoradas. - processing_class (PreTrainedTokenizerBase, opcional) —
Classe de processamento usada para processar os dados. Se for
None, ela é carregada a partir do nome do modelo com from_pretrained. Se não tiver um token de padding,tokenizer.eos_tokené usado. - callbacks (lista de TrainerCallback, opcional) — Lista de callbacks para personalizar o loop de treinamento, adicionados aos callbacks padrão (veja aqui).
- optimizers (
tuple[torch.optim.Optimizer | None, torch.optim.lr_scheduler.LambdaLR | None], opcional, padrão(None, None)) — Uma tupla contendo o otimizador e o agendador a serem usados. O padrão éAdamWe um agendamento linear controlado porargs. - peft_config (PeftConfig, opcional) —
Configuração PEFT usada para envolver o modelo. Se for
None, o modelo não é envolvido. Quando o servidor vLLM do aluno foi iniciado com--enable-lora, um adaptador LoRA é sincronizado com ele como um adaptador, em vez de como pesos mesclados; consulte a seção LoRA da documentação do AsyncDistillation.
Contraparte assíncrona de DistillationTrainer, arquitetada exatamente como AsyncGRPOTrainer: um worker de rollout em segundo plano gera as conclusões on-policy do aluno e as pontua em relação a um servidor de professor enquanto o treinamento prossegue de forma concorrente, desacoplando o rollout do loop de atualização de gradiente. Onde a perda de gradiente de política com clipping do GRPO lê uma vantagem relativa ao grupo, o compute_loss deste trainer lê uma distribuição de professor esparsa por posição e minimiza um JSD generalizado em relação a ela (veja AsyncDistillationConfig.beta), sempre on-policy: o aluno gera cada conclusão em que ele treina.
Diferentemente do DistillationTrainer síncrono, este trainer suporta apenas a pontuação contra um teacher servido via HTTP (um servidor vLLM externo). Uma passagem de forward de um teacher local (in-process, GPU) não pode ser executada dentro do processo filho gerado pelo worker de rollout, que tem CUDA desabilitado, da mesma forma que as funções de recompensa do GRPO podem, e precisaria, em vez disso, ser executada de volta no processo principal de treinamento; esse caminho ainda não foi implementado.
Exemplo:
>>> from trl.experimental.async_distillation import AsyncDistillationTrainer
>>> from datasets import load_dataset
>>> dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
>>> trainer = AsyncDistillationTrainer(
... model="Qwen/Qwen2.5-0.5B-Instruct",
... train_dataset=dataset,
... )
>>> trainer.train()