Skip to content

feat: frequency, min_new_tokens, presence, and repetition penalties - #973

Merged
Ying1123 merged 7 commits into
sgl-project:mainfrom
tmfi-us:ryan/srt/schedule_batch/implement-penalties
Aug 8, 2024
Merged

Ying1123 merged 7 commits into
sgl-project:mainfrom
tmfi-us:ryan/srt/schedule_batch/implement-penalties

Conversation

@vhain

@vhain vhain commented Aug 7, 2024

Copy link
Copy Markdown
Contributor

Thank you for your contribution, we really appreciate it. The following instructions will help improve your pull request and make it easier to receive feedback. If there are any items you don't understand, don't worry. Just submit the pull request and ask the maintainers for help.

Motivation

Penalties are defined in SamplingParams, and the tensors being created during batch execution, but not yet implemented.

Also we can cut-down some milliseconds in ITL, and some VRAM usage, by not creating those unnecessary tensors.

Modification

  • Implemented following penalties, similarly to they are in vllm:
    • frequency_penalty
    • min_new_tokens
    • presence_penalty
    • repetition_penalty
  • Introduces following new sampling_parameters, and added them to OpenAI protocol in same name as appeared on vllm for compatibility:
    • stop_token_ids: similar to stop, but token IDs instead of str. This could be useful when used with min_new_tokens.
    • repetition_penalty
    • min_new_tokens
  • Each penalizers are fully batch-compatible (filter, merge supported).
  • Penalizers will do nothing (not even create a tensor) if they are not required.
  • Performance implcations on using penalizer sampling_parameters are documented.
  • Add ability for sglang.bench_serving to accept additional --request-body to specify sampling_params if desired.

Benchmark

Benchmarks were ran on single NVIDIA H100 SXM5

Baeline calculated on dc9d06d:

$ python3 -m sglang.bench_serving --backend sglang --port 8413 --dataset-name random --num-prompts 3000 --random-input 256 --random-output 512

============ Serving Benchmark Result ============
Backend:                                 sglang
Traffic request rate:                    inf
Successful requests:                     3000
Benchmark duration (s):                  67.50
Total input tokens:                      378633
Total generated tokens:                  775651
Total generated tokens (retokenized):    774862
Request throughput (req/s):              44.44
Input token throughput (tok/s):          5609.11
Output token throughput (tok/s):         11490.58
----------------End-to-End Latency----------------
Mean E2E Latency (ms):                   42141.26
Median E2E Latency (ms):                 45393.52
---------------Time to First Token----------------
Mean TTFT (ms):                          20705.28
Median TTFT (ms):                        14893.99
P99 TTFT (ms):                           49444.93
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          94.00
Median TPOT (ms):                        91.96
P99 TPOT (ms):                           232.66
---------------Inter-token Latency----------------
Mean ITL (ms):                           181.11
Median ITL (ms):                         62.46
P99 ITL (ms):                            441.66
==================================================

Calculated on this PR's branch (without any penalizers):

$ python3 -m sglang.bench_serving --backend sglang --port 8413 --dataset-name random --num-prompts 3000 --random-input 256 --random-output 512

============ Serving Benchmark Result ============
Backend:                                 sglang
Traffic request rate:                    inf
Successful requests:                     3000
Benchmark duration (s):                  66.11
Total input tokens:                      378633
Total generated tokens:                  775651
Total generated tokens (retokenized):    775118
Request throughput (req/s):              45.38
Input token throughput (tok/s):          5727.04
Output token throughput (tok/s):         11732.16
----------------End-to-End Latency----------------
Mean E2E Latency (ms):                   40881.94
Median E2E Latency (ms):                 43967.10
---------------Time to First Token----------------
Mean TTFT (ms):                          19884.75
Median TTFT (ms):                        14226.56
P99 TTFT (ms):                           47738.97
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          91.96
Median TPOT (ms):                        90.11
P99 TPOT (ms):                           308.54
---------------Inter-token Latency----------------
Mean ITL (ms):                           174.54
Median ITL (ms):                         58.56
P99 ITL (ms):                            440.18
==================================================

Additional benchmarks can be found in the updated sampling_params.md

Notes to Reviewers

This is my very first time writing Python code, and thus working with PyTorch (my industry backgrounds are primarily Golang, Rust, Java, Nodejs, ...). So please feel to add any comments if something looks off or inefficient.

Checklist

  • 1. Ensure pre-commit pre-commit run --all-files or other linting tools are used to fix potential lint issues.
  • 2. Confirm that modifications are covered by complete unit tests. If not, please add more unit tests for correctness.
  • 3. Modify documentation as needed, such as docstrings or example tutorials.

@vhain
vhain force-pushed the ryan/srt/schedule_batch/implement-penalties branch from ae3a86c to edaab22 Compare August 7, 2024 16:10
@zhyncs

zhyncs commented Aug 7, 2024

Copy link
Copy Markdown
Contributor

@vhain Nice work!

@merrymercy merrymercy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contributions! The code quality is very high and it is well tested. Great work! I only have few minor style comments.

Comment thread docs/en/sampling_params.md
Comment thread python/sglang/bench_serving.py Outdated
Comment thread test/srt/sampling/penaltylib/penalizers/test_min_new_tokens.py Outdated
Comment thread test/srt/sampling/penaltylib/test_srt_endpoint_with_penalizers.py Outdated
Comment thread docs/en/sampling_params.md
Comment thread docs/en/sampling_params.md
Comment thread docs/en/sampling_params.md
Comment thread docs/en/sampling_params.md
Comment thread python/sglang/srt/managers/schedule_batch.py
@Ying1123

Ying1123 commented Aug 8, 2024

Copy link
Copy Markdown
Contributor

@vhain ready to merge?
@zhyncs can you take another look and approve this?

@Ying1123

Ying1123 commented Aug 8, 2024

Copy link
Copy Markdown
Contributor

In the future, we plan to wrap all the logits processing and sampling into the Cuda graph as well.
Are these penalizers cuda-graph compatible?

@zhyncs zhyncs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall LGTM

@zhyncs

zhyncs commented Aug 8, 2024

Copy link
Copy Markdown
Contributor

In the future, we plan to wrap all the logits processing and sampling into the Cuda graph as well. Are these penalizers cuda-graph compatible?

@vhain

@Ying1123
Ying1123 merged commit ab78759 into sgl-project:main Aug 8, 2024
@vhain

vhain commented Aug 8, 2024

Copy link
Copy Markdown
Contributor Author

@Ying1123 @zhyncs I'm still pretty new to this ground (started playing with LLM ~ month ago, started using sglang ~ 2 weeks ago, and this is my first ever Python/PyTorch code written). I don't think this penalizers are implemented based on CUDA grapu, but I will take a look on other CUDA graph implementations in sglang to figure it out. Hopefully I can pull up another PR to make it use CUDA graphs...

@vhain

vhain commented Aug 8, 2024

Copy link
Copy Markdown
Contributor Author

@Ying1123 @zhyncs Although I might need some extra time on what and how the CUDA graph works, all those penalizers are purely written in PyTorch (with device="cuda"). So it should be fairly simple to migrate..?

@merrymercy

merrymercy commented Aug 8, 2024

Copy link
Copy Markdown
Contributor

Yes, do not worry about it for now. We will refactor and start moving the sampling code to be within the CUDA graph. We will let you know later and ask for your help if needed.

@Xu-Chen

Xu-Chen commented Sep 11, 2024

Copy link
Copy Markdown
Contributor

We found that presence_penalty has a great influence on the speed of decode.
When set presence_penalty = 0.5 qwen-72b-gptq-int4 ( ~17 token/s on 4 * A100),3.x slower than set presence_penalty=0.0 (~60 token/s on 4*A100).
cc @merrymercy @zhyncs

This can be reproduced in version 0.3.0, but does not occur in version 0.2.13.

@merrymercy

Copy link
Copy Markdown
Contributor

@hnyls2002 Is this related to how the cuda graph is triggered?

@merrymercy

merrymercy commented Sep 11, 2024

Copy link
Copy Markdown
Contributor

@Xu-Chen Recently, we did some modifications to how the cuda graph is enabled. In 0.3.0, if you set penalty or regex, cuda graph will be disabled. This is a bug and we will fix it soon.

@hnyls2002

Copy link
Copy Markdown
Collaborator

@vhain @Xu-Chen I believe the performance slowdown problem will be fixed in #1392.

With llama3.1-8b on 1 A100, the decoding speed is 77 tokens per sec the same as without presence penalty, but on main the speed is 71 tokens per sec.

On 4 x A100, the decoding speed is 129 tokens per sec, while without presence penalty the speed is 134 tokens per sec, but on main the speed is 58 tokens per sec.

@hnyls2002

hnyls2002 commented Sep 14, 2024

Copy link
Copy Markdown
Collaborator

@vhain @Xu-Chen @merrymercy
The penalties consume calculation in two phases:

  • Cumulate output tokens or input tokens.
  • Apply the penalties to the logits results.

The second phase was fully overlapped by

batch.sampling_info.update_penalties()
(this calculation can be executed parallel with model forward)

Also, the first phase can be overlapped, I have made some tries, but it failed.

dc16725
3273d3f

What I thought was to make the cumulate a lazy operation, only being done when batch size changes or before applying. This can save more time and make the inference with penalties as fast as in normal cases. If you are interested in this, you are welcome to contribute to this optimization.

JinyanYi pushed a commit to JinyanYi/sglang that referenced this pull request Jul 10, 2026
Update test_npu_llava_onevision_qwen2_7b_ov.py
efschu pushed a commit to efschu/htsglang that referenced this pull request Aug 28, 2026
…he silent hop

Boot 2 of window-flip-0828 wedged silently for 10+ minutes: PP0 and PP1
blocked in gloo waitSend under _pp_commit_comm_work's naked
p2p_work.work.wait() (PP1 via _pp_commit_pending_req_work), PP2 in
_do_recv -- a closed three-arc cycle on a group whose own timeout is two
hours. The sgl-project#753 comment in this file already described the exact shape
(boot v7pp9); the ordering was fixed then, the wait stayed naked.

The bound routes through the sgl-project#630/sgl-project#829 canon
(hicache_collective.bounded_wait -> ParkedWait): the unbounded wait()
parks on a thread and the deadline is on the JOIN. Deliberate deviation
from the order's 'pass the deadline into work.wait(timeout)': sgl-project#829
retracted that design in this tree -- an expired timed Work.wait closes
the gloo pair, and hicache_collective.py names _pp_commit_comm_work as a
measured victim (34 of 262 boot logs). Budget 120 s
(SGLANG_PP_RING_COMMIT_BUDGET_S; <= 0 = documented escape hatch,
byte-for-byte pre-sgl-project#973), reasoned against the canon's constants: 12.6x
the longest healthy cutover, 4x the sgl-project#789 budget, 5x under the HiCache
bound, 60x under the group timeout. On expiry: RingCommitTimeout with a
sgl-project#650-style peer statement naming the silent hop; transport failure is
NOT converted (sgl-project#734 stays distinguishable). Helpers are module-level,
not methods -- the method form broke ~12 one-method-at-a-time stand-in
holders (measured: 796 5->2, 801 14->3), recorded in a code comment.

Wait-site audit: :5253 bounded-now; :3270 bounded by delegation (proven
by test); _do_recv/:7592 and :5545 bounded-already via the sgl-project#789 gate;
parallel_state.py recv_object stays unbounded and is NAMED as its own
posten (needs the resumable PpChainReceiver treatment, not a terminal
bound mid-protocol).

Tests: test_ring_commit_bounded_973.py, 5 arms on real 3-process gloo --
pre-sgl-project#973 naked commit HANGS (distinguished from raised via fsynced
progress marker), bounded commit raises within budget with the peer
statement, healthy paired traffic unchanged, escape-hatch mutant hangs
again (can-fail), :3270 reachability arm. Gates: BEFORE == AFTER
byte-identical on 796(5)/801(14)/797(31)/630(5+14)/829(7) plus an
18-module sibling sweep; extraction count probe OK on every run.
ruff/codespell: 0 new findings.
efschu pushed a commit to efschu/htsglang that referenced this pull request Aug 28, 2026
 void-relay launched-verdict) into merge/flip-window-0828

Root fix for boot 2's ring wedge: pp_void_relay_launched_verdict stops a
retraction-less zero-offer void from travelling to a never-launched rank
(unmatched -> stash -> positional mis-consume by the next healthy
generation -> one rank's live batch emptied -> ring one message short).
Legacy byte-identical when the launched chain is absent. Terrain overlap
with sgl-project#973/sgl-project#974 in scheduler_pp_mixin.py reviewed at merge.
efschu pushed a commit to efschu/htsglang that referenced this pull request Aug 28, 2026
…ed chain

The merge gate on b3a2a59 surfaced the same interface drift sgl-project#978's own
branch repaired once in test_pp_retracted_pass_void_797.py, in two more
modules its neighbour set did not include: the event loop now passes
launched_chain= to _pp_send_admission_decision (798's recorder stub:
TypeError unexpected keyword) and calls _pp_note_launched_chain (631's
_Rank: AttributeError -- it takes shipping methods unbound one name at a
time). Repairs follow the in-file conventions: 798's recorder accepts and
ignores the kwarg exactly as it ignores expects_output (the 797 repair
verbatim); 631's _Rank takes the SHIPPING _pp_note_launched_chain unbound
like its twelve neighbours -- its own docstring guarantees stand-in
tolerance (sgl-project#787). No assertion touched. Solo re-runs: 631 6 passed,
798 7 passed; merge-tip failure set now byte-identical to baseline
(the two known test_collective_family_siblings_610 assertions).

Named for the sgl-project#978 desk strand, not fixed here: _pp_note_launched_chain
as a self.-method repeats the one-name-at-a-time holder-binding class
that sgl-project#973 and sgl-project#974 each just converted to module-level functions after
measured breakage; third module falls to it here.
efschu pushed a commit to efschu/htsglang that referenced this pull request Sep 15, 2026
… Prefetch-Verdikt TOLD auf der Request-Leitung

Befund (Boot xsn116, 2026-09-15 17:32:17, rid e10588ca, zweite Anfrage mit
identischem Praefix, D hatte ihn durchgeschrieben): jeder P-Rang registriert
seinen HiCache-Storage-Prefetch, keiner wartet darauf (sgl-project#973 PP0 PREFETCH WAIT
DISARMED / #969Z), der spaeter fertige Host-Treffer wird als sgl-project#1245
'undistributable' verworfen, damit die Raenge gleich bleiben -- P prefillt
einen im Store byte-vollstaendigen Praefix neu (STORE WITNESS state=unprobed
loaded=0, 'sgl-project#928 anchor REFUSING resume ... re-prefilling'). D leg 2 liest
denselben Store korrekt (cached_tokens=4314/4316), weil der TP-Pfad wartet
(Policy timeout) und MIN-reduziert.

Fix: der Traeger ist die Request-Leitung, die es schon gibt (recv_reqs wird
je Pass PP0->PP1->PP2 gepickelt weitergereicht; sgl-project#1268 fix 1c faehrt dort
schon Weg2IdleVoteReq). Protokoll je rid:
  1. Intake: PP0 registriert wie bisher und haelt; Folgestufe registriert
     NICHTS ('declined:weg2_held', neuer sgl-project#915-Partitionsterm).
  2. PP0 vor dem Forward: fuer jeden gehaltenen rid mit terminiertem
     Prefetch (check_prefetch_progress, kollektivfrei bei tp_size 1) wird
     die geladene Zahl ohne Pop gelesen, als told gespeichert und als
     Weg2StoreTold(rid, told) auf die Leitung gehaengt. PP0 admittet nur,
     was es auf die Leitung gelegt hat.
  3. Folgestufe nach dem Forward, vor dem Dispatch: nimmt die Objekte aus
     der Liste, speichert told, registriert den Prefetch fuer EXAKT told
     Token (_prefetch_kvcache(limit_tokens=told)) -- gleiche Seiten, gleiche
     Laenge, gleicher Store: Host-Baum und Load-Back-Extent sind inhaltlich
     uniform, keine Praefix-Truncation an einem Mamba-Anker noetig.
  4. Admission auf jedem Rang: ohne told -> Skip 'weg2_store_told_pending';
     mit told -> bounded Warten auf den eigenen Read, Pop, und benannte
     Verweigerung Weg2StoreToldMismatch bei own != told (Raenge nie uneins).
Der sgl-project#1245-Drop bleibt fuer die traegerlose Form ohne told; mit told ist der
Extent verteilbar. Arm nur bei pp_size>1, tp_size==1, Storage an, kein
Zeilentraeger; Kill-Switch SGLANG_WEG2_STORE_TOLD=0. pp_size<=1 (Gruppe D)
unveraendert.

Tests: test_weg2_store_told_1400.py 12 gruen; Mutant (Admission ohne Verdikt
+ Mismatch-Refusal aus) 3 rot. 16 Test-Dateien der beruehrten Symbole:
34 rot vor und nach der Aenderung identisch (Sockel: Doubles ohne ps /
_pp_post_send / _pp_send_admission_decision), 0 neu. Provider-Smoke 19/19.
Metall-Abnahme steht aus (arm_xsn117): zweite Anfrage gleicher Praefix ->
P leg 1 cached_tokens>0 und 0x 'sgl-project#928 anchor REFUSING'.
efschu pushed a commit to efschu/htsglang that referenced this pull request Sep 15, 2026
…tore read: partial hit = admit

Boot xsn121 (991e1ea), rid 06089842 (Q1 prefix + 6.8k appendix): the
told carrier now lands (PP0 told=4095, PP1 ABSORBED/WAITED, 'sgl-project#988 LOADBACK
prefix moved to 4095', no mismatch), but PP0 then went through the sgl-project#1324
'STORE READ INCOMPLETE delivered=4095 deliverable=11126 shortfall=7031'
arm (A12.2 store_prefix_short deferral), re-issued the 7031-token remainder
every pass ('sgl-project#1035 PREFETCH DROPPED host anchor pool exhausted avail=0
size=4', 'sgl-project#915 PREFETCH REFUSED alloc_failed_post_vote'), sat
'ADMISSION-WEDGE: 1 queued, 0 running' for 120 s and died on
'sgl-project#973 RING COMMIT TIMEOUT' -- while PP1 had admitted (extent 4095..8191).

The arm was built for the single-stage CONSUMER (group D, leg 2: P's
write-through still in flight, boot weg2sn6s). On a PP prefill group the
store has no producer to wait for -- the group itself prefills the rest --
so a read that terminated short is a partial hit to admit.
`_weg2_note_store_shortfall` returns None on `ps.pp_size > 1`; the
single-stage form is byte-identical (no ps on the harness doubles -> 1).
Tests: test_weg2_store_consistency_1324 (+1: PP group admits the partial
hit) + test_weg2_store_told_1400: 39 passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants