forked from verl-project/verl
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
494 lines (459 loc) · 20.4 KB
/
Copy pathpyproject.toml
File metadata and controls
494 lines (459 loc) · 20.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "verl"
dynamic = ["version", "dependencies", "authors", "urls"]
description = "verl: Volcano Engine Reinforcement Learning for LLM"
license = {text = "Apache-2.0"}
readme = {file = "README.md", content-type = "text/markdown"}
requires-python = ">=3.10,<3.13"
# One universal uv.lock: each backend is a PEP 621 extra; mutually exclusive ones
# are in [tool.uv].conflicts, so a single `uv lock` resolves them all. At runtime,
# sync one conflict-free combo into .venv (shared by every Ray worker group):
#
# uv sync --extra fsdp --extra vllm # or: python manage_envs.py sync fsdp vllm
#
# All backends use torch 2.11.0: vllm/sglang/fsdp/megatron on cu130, cpu on cpu
# wheels (CI/dev). Both torch worlds coexist via [tool.uv].conflicts forks.
# DEFERRED: trtllm (torch 2.10.0+cu130) — its RC sdist makes `uv lock` explode.
# cuDNN/NCCL: torch's wheel pins them, so after `uv lock` sync CUDNN_VERSION /
# NCCL_VERSION in docker/Dockerfile.uv.cu130 to the resolved values.
[project.optional-dependencies]
# ---- legacy small extras (kept verbatim from setup.py) ---------------------
test = ["pytest", "pre-commit", "py-spy", "pytest-asyncio", "pytest-rerunfailures"]
math = ["math-verify"]
mcore = ["mbridge"]
# Named extra so the git source lives in one place; verl-core pulls it in, so it
# is always installed (no longer opt-in).
transferqueue = ["TransferQueue @ git+https://github.com/Ascend/TransferQueue.git@main"]
# Shared runtime deps, included by every backend via `verl[verl-core]`.
# TransferQueue is always installed (imported at top level on core paths).
verl-core = [
"verl[transferqueue]",
"accelerate",
"codetiming",
"datasets",
"dill",
"hydra-core",
"numpy>=2.0.0",
"pandas",
"peft",
"pyarrow>=19.0.0",
"pybind11",
"pylatexenc",
# Unversioned: override-dependencies below sets the version per backend.
"transformers",
"ray[default]>=2.41.0",
"torchdata",
"tensordict>=0.8.0,<=0.10.0,!=0.9.0",
"wandb",
"packaging>=20.0",
"tensorboard",
"fastapi",
"uvicorn",
"mathruler",
"qwen_vl_utils",
# qwen-vl-utils' first-choice video decoder; it falls back to
# torchvision.io.read_video, which torchvision 0.26 removed, so without
# torchcodec every video sample dies with AttributeError. It reached only
# the sglang venv (a transitive sglang dep), leaving vllm / fsdp / megatron
# / cpu broken — hence verl-core. Left unpinned the way sglang has it: it is
# ABI-coupled to torch and every extra here pins the same torch 2.11.0.
# Decoding needs FFmpeg's shared libs (`ffmpeg`, docker/Dockerfile.uv.cu130).
"torchcodec",
"cachetools",
"nvtx",
"pytest",
"pytest-asyncio",
"pytest-rerunfailures",
]
# Pins flash-attn once for the fsdp / megatron parents (wheelhouse-routed below).
# Don't install alone — needs the parent extra's torch / cuDNN pins.
flash-attn-cu130torch211 = ["flash-attn==2.8.3"]
# Pins cupy once for the fsdp / megatron parents. Two unrelated consumers, both
# on the training side: `ray.util.collective`'s NCCL backend is cupy-based, so
# every disaggregated weight-sync engine (nccl / nixl / delta_sharded) imports
# it; and Megatron MoE all-to-all uses it directly. Without it the engine module
# fails to import and the backend never reaches CheckpointEngineRegistry.
cupy-cu130 = ["cupy-cuda13x==14.0.1"]
# ---- inference backends ----------------------------------------------------
vllm = [
"verl[verl-core]",
"torch==2.11.0",
"torchvision==0.26.0",
"torchaudio==2.11.0",
# PyPI's 0.24.0 wheel is already a cu130 / torch-2.11 abi3 build, so it needs
# no wheelhouse routing and no source build.
"vllm==0.24.0",
# flashinfer is a transitive dep vllm 0.24 hard-pins to ==0.6.12 (pure-python
# from PyPI); the wheelhouse's 0.6.13 doesn't match, so it isn't routed.
"mbridge",
# vllm 0.24 needs transformers>=5.5.3; 5.9.0 is the project-wide pin
# (enforced in override-dependencies below).
"transformers==5.9.0",
]
sglang = [
"verl[verl-core]",
"torch==2.11.0",
"torchvision==0.26.0",
"torchaudio==2.11.0",
# sglang + sglang-kernel come from PyPI; upstream's 0.5.12 wheels are cu130 /
# torch-2.11 builds already (sglang-kernel links libcudart.so.13).
"sglang==0.5.12",
# sglang pins the kernel itself (==0.4.2.post2); kept explicit so the backend
# still installs it if a future sglang loosens that pin.
"sglang-kernel",
# sglang hard-deps flash-attn-4 but we EXCLUDE it (it collides with the
# flash-attn 2.8.3 the training extras pin — see override-dependencies); it is
# imported lazily (fa4 backend only), so the default backends work.
"mbridge",
# sglang 0.5.12 hard-pins transformers==5.6.0; the project-wide override below
# replaces that pin with 5.9.0 (5.6.0 is excluded repo-wide, see setup.py).
"transformers==5.9.0",
]
# DEFERRED — trtllm is a CUDA-13 backend (torch 2.10.0+cu130, conflict-gated).
# tensorrt-llm 1.3.0rc14's tightly-pinned RC tree makes `uv lock` explode. To
# re-enable on a stable release: uncomment, add cu130 torch routing + a
# `{ extra = "trtllm" }` conflict entry (both flagged below), and re-lock.
# trtllm = [
# "verl[verl-core]",
# "torch==2.10.0", # route to pytorch-cu130 (CUDA 13), see [tool.uv.sources]
# "tensorrt-llm==1.3.0rc14 ; sys_platform == 'linux' and platform_machine == 'x86_64'",
# "mbridge",
# "transformers==5.9.0",
# "numpy>=2.0.0,<2.4",
# "ray[default]==2.54.1",
# ]
# ---- training backends -----------------------------------------------------
fsdp = [
"verl[verl-core]",
"verl[flash-attn-cu130torch211]",
"verl[cupy-cu130]",
"torch==2.11.0",
"torchvision==0.26.0",
"torchaudio==2.11.0",
"liger-kernel>=0.8.2",
"trl==0.27.0",
# transformers NOT pinned here: verl-core carries it and the project-wide
# override below settles the version (5.9.0). See override-dependencies.
]
# apex / TE / flash-attn / megatron-bridge come prebuilt from the wheelhouse;
# megatron-core is git-sourced (built at sync time). onnxscript: works around an
# undeclared TE import.
megatron = [
"verl[verl-core]",
"verl[flash-attn-cu130torch211]",
"verl[cupy-cu130]",
"torch==2.11.0",
"torchvision==0.26.0",
"torchaudio==2.11.0",
"nvidia-mathdx",
"apex",
"transformer-engine",
"onnxscript",
"trl==0.27.0",
"matplotlib",
"liger-kernel>=0.8.2",
# Two Megatron<->HF connectors: mbridge (legacy) and megatron-bridge (NVIDIA,
# imported as megatron.bridge; the migration target, installed deps-free — see
# [[tool.uv.dependency-metadata]] below). 0.5.2 is the wheelhouse build of the
# r0.5.0 release branch.
"mbridge",
"megatron-bridge==0.5.2",
"megatron-core",
# megatron-bridge hard-imports modelopt but is installed deps-free (below), so
# megatron needs it explicitly. 0.44.0: PyPI stable of bridge r0.5.0's 0.44.x.
"nvidia-modelopt==0.44.0",
# transformers NOT pinned here (see fsdp note): settled via override-deps below.
]
# ---- CI-only extras --------------------------------------------------------
# Layered by .github/workflows on top of a backend combo so CI deps come from
# the lock (`sync <backend...> ci`) instead of ad-hoc `uv pip install`. No
# backend depends on these, so runtime images never carry them unless asked.
#
# mlflow is deliberately NOT here: it caps pandas<3 and cryptography<49, and
# because `ci` shares one resolution fork with every backend those caps would
# drag the whole project from pandas 3.0.3 to 2.3.3. gpu_unit_tests installs it
# by hand for that reason — do not "fix" that by moving it in here.
ci = [
"hf-transfer", # HF_HUB_ENABLE_HF_TRANSFER=1 in the GPU test jobs
"sglang-router==0.2.2", # router coverage in reward_model_sglang
]
# veomni drives the SFT engine tests. Installed deps-free (empty requires-dist
# in [[tool.uv.dependency-metadata]] below): its real metadata pins
# datasets<=2.21.0 and a cu129 torch, both of which fight the cu130 backends.
# This mirrors the `--no-deps` install the SFT workflows did by hand.
veomni-sft = ["veomni==0.1.11"]
# CI / unit tests / dev sanity. No GPU runtime.
cpu = [
"verl[verl-core]",
"torch==2.11.0",
"torchvision==0.26.0",
"torchaudio==2.11.0",
"transformers==5.9.0",
# Plain source package (torch / numpy / packaging + one pybind11 C++ ext, no
# CUDA toolchain), and it imports without TE / apex — both are optional there
# with pure-torch fallbacks. It is what lets the `*_on_cpu.py` tests that
# exercise verl's Megatron wiring run here instead of being skipped. The
# CUDA-only half of the megatron extra (apex / TE / megatron-bridge) stays out.
"megatron-core",
# megatron.core.transformer.moe.paged_stash `import triton` unguarded, and
# `import megatron.core` reaches it. megatron-core doesn't declare triton
# because on the GPU backends torch's cu130 wheel already pulls it in; the cpu
# wheel doesn't, so the cpu slice has to ask for it explicitly.
"triton",
"pytest",
"pre-commit",
"py-spy",
"pytest-asyncio",
"pytest-rerunfailures",
]
[tool.ruff]
line-length = 120
exclude = ["scripts/legacy_model_merger.py"]
[tool.ruff.lint]
isort = {known-first-party = ["verl"]}
# c.f. https://github.com/vllm-project/vllm/blob/ce8d6b75fc0586045df75ee1568a5b5f9957251b/pyproject.toml
select = [
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# flake8-bugbear
"B",
# isort
"I",
"G",
]
ignore = [
# star imports
"F405", "F403",
# lambda expression assignment
"E731",
# Loop control variable not used within loop body
"B007",
# f-string format
"UP032",
# `.log()` statement uses f-string
"G004",
# X | None for type annotations
"UP045",
# deprecated import
"UP035",
]
[tool.mypy]
pretty = true
ignore_missing_imports = true
explicit_package_bases = true
follow_imports = "skip"
ignore_errors = true
[[tool.mypy.overrides]]
module = [
"verl.trainer.config.algorithm",
"verl.trainer.ppo.core_algos",
"verl.trainer.ppo.reward",
"verl.workers.reward_manager",
"verl.workers.reward_manager.*",
]
ignore_errors = false
[tool.setuptools]
include-package-data = true
[tool.setuptools.dynamic]
version = {file = "verl/version/version"}
[tool.setuptools.package-dir]
"" = "."
[tool.setuptools.package-data]
verl = [
"version/*",
"trainer/config/*.yaml",
"trainer/config/*/*.yaml",
"experimental/*/config/*.yaml",
]
# One universal uv.lock: `uv lock` resolves every extra; conflicting extras
# coexist in the lock but never in one venv. See docs/start/install.rst.
[tool.uv]
# Linux + Python 3.12 on both CPU arches verl ships CUDA images for: x86_64 and
# aarch64 (GH200 / GB200 superchips).
#
# Arch is a RESOLUTION dimension, NOT an extra dimension. Every backend extra
# above is spelled exactly once, and `uv sync --extra megatron` installs the
# right wheels on either host: uv forks the lock per marker and then matches
# each wheel's own platform tag. Extra names, conflict sets, manage_envs.py
# combos and every CI invocation are identical on the two arches.
#
# That works because everything the cu130 backends need is published for both:
# torch / torchvision / torchaudio cu130 (download.pytorch.org ships
# manylinux_2_28_aarch64), vllm / sglang / sglang-kernel / cupy-cuda13x /
# sglang-router (PyPI), and the three native wheelhouse builds apex /
# flash-attn / transformer-engine (the wheelhouse builds those on
# ubuntu-24.04-arm with TORCH_CUDA_ARCH_LIST "9.0;10.0" — the only CUDA parts an
# arm64 wheel can land on). megatron-bridge is py3-none-any; megatron-core /
# mbridge build from git. The second entry costs one extra package (sglang's
# aarch64-only decord2) and leaves every pinned version unchanged.
#
# If a package ever IS arch-specific, do NOT fork the backend into
# `megatron-x86_64` / `megatron-aarch64` extras — that doubles the conflict
# matrix and every call site. Split it INSIDE the extra with a marker, either on
# the requirement itself:
#
# "some-x86-only-pkg ; platform_machine == 'x86_64'",
#
# or, for more than one package, as a marker-gated sub-extra (the same
# convention flash-attn-cu130torch211 / cupy-cu130 use to share a pin):
#
# megatron-x86_64 = ["pkg-a", "pkg-b"] # in the extras
# "verl[megatron-x86_64] ; platform_machine == 'x86_64'", # inside megatron
#
# Either way `--extra megatron` stays the one thing anybody types.
environments = [
"python_full_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'x86_64'",
"python_full_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'aarch64'",
]
# Mutually exclusive extras — at most one per set per `uv sync`. Distinct torch
# worlds never mix (cu130: vllm/sglang/fsdp/megatron; cpu). vllm/sglang/cpu are
# mutually exclusive; cpu also excludes fsdp/megatron. fsdp/megatron CAN combine
# with a cu130 inference engine (e.g. --extra megatron --extra vllm).
conflicts = [
# (trtllm DEFERRED — add `{ extra = "trtllm" }` to each set when it rejoins.)
[{ extra = "vllm" }, { extra = "sglang" }, { extra = "cpu" }],
[{ extra = "fsdp" }, { extra = "cpu" }],
[{ extra = "megatron" }, { extra = "cpu" }],
]
# Global override pins:
# * transformers==5.9.0 project-wide — one version for every backend, so a
# venv's transformers no longer depends on which engine it was synced with.
# Unmarked on purpose: overrides are absolute and replace EVERY requirement
# on the package, so this also settles the engines' own pins (vllm 0.24 asks
# >=5.5.3, sglang 0.5.12 hard-pins ==5.6.0 — a release excluded repo-wide,
# see setup.py). Keep it inside setup.py / requirements.txt's supported
# window (>=5.5.3,!=5.6.0,<5.11) and bump all of them together. Do NOT add
# an `extra` marker back unless a backend truly needs to diverge: such a
# marker matches only a requirement verl declares under that extra, never a
# transitive one, and a fork no override matches drops transformers outright.
# * numpy>=2.0.0 floor.
# * kernels>=0.12.0,<0.13 — sglang deps `kernels` unbounded; later releases
# broke transformers' hub_kernels.py before (huggingface/transformers#46291),
# so pin to transformers 5.9.0's own window (satisfies sglang too) and move
# it whenever the transformers pin moves.
# * flash-attn-4 (sglang's hard dep) is DROPPED — its marker is false in every
# declared environment, which removes the requirement outright. It ships the
# same `flash_attn/cute/` files as flash-attn 2.8.3 (the fsdp / megatron pin),
# so installing both leaves whichever landed last on disk. TransformerEngine
# imports flash_attn.cute.interface as soon as flash-attn-4's dist metadata
# exists, so once 2.8.3's older copy wins, `import megatron.core` dies with
# "No module named 'cutlass.utils.ampere_helpers'" — a module the
# nvidia-cutlass-dsl 4.5 that flashinfer pulls in no longer has.
# docker/Dockerfile.stable.sglang settles the same clash with
# `pip uninstall -y flash-attn-4`; sglang itself only imports fa4 for its
# non-default "fa4" attention backend.
override-dependencies = [
"transformers==5.9.0",
"numpy>=2.0.0",
"kernels>=0.12.0,<0.13",
"flash-attn-4 ; platform_machine == 'flash-attn-4-excluded'",
# aarch64 only: nvidia-cusparselt-cu13 0.8.0/0.8.1 ship an internal WHEEL tag
# of `manylinux2014_sbsa` while the filename says `aarch64`. uv rejects the
# mismatch and reinstalls the package on EVERY `uv run`; 100+ concurrent Ray
# workers then race deleting libcusparseLt.so.0 and crash torch import
# (astral-sh/uv#17711). 0.9.1 fixed the internal tag to `manylinux2014_aarch64`
# and moves the lib to nvidia/cu13/lib/, which torch 2.11's _preload_cuda_deps
# already searches ("nvidia/cuXX/lib since CUDA 13.0"). SONAME stays .so.0.
# x86_64 keeps 0.8.0 (its tag is correct; no reinstall) to avoid churning a
# working config.
"nvidia-cusparselt-cu13==0.8.0 ; platform_machine == 'x86_64' and sys_platform == 'linux'",
"nvidia-cusparselt-cu13==0.9.1 ; platform_machine == 'aarch64' and sys_platform == 'linux'",
]
# Skip lock-time metadata builds for git-source packages with no [project] /
# dynamic version (the sync-time source build still runs). Versions track
# [tool.uv.sources] below; bump together. (Wheelhouse packages read metadata from
# the wheel — no entry needed, except megatron-bridge, whose wheel metadata we
# deliberately discard; see below.)
[[tool.uv.dependency-metadata]]
name = "megatron-core"
version = "0.18.0"
requires-dist = ["torch>=2.6.0", "numpy", "packaging>=24.2"]
[[tool.uv.dependency-metadata]]
name = "mbridge"
version = "0.15.1"
requires-dist = []
# megatron-bridge 0.5.2 comes prebuilt from the wheelhouse (megatron-bridge-r0.5.0
# tag), pairing with megatron-core 0.18.0. The wheel's real metadata pulls a
# conflicting transformers + NeMo stack (plus its own flashinfer pins), so we
# declare EMPTY requires-dist (like `pip install --no-deps`) — the megatron extra
# already provides torch / megatron-core / TE.
[[tool.uv.dependency-metadata]]
name = "megatron-bridge"
version = "0.5.2"
requires-dist = []
# veomni 0.1.11 (veomni-sft extra) is likewise installed deps-free: its metadata
# pins datasets<=2.21.0 and a cu129 torch/torchvision/torchaudio set that cannot
# coexist with the cu130 backends. The SFT tests only need veomni's own modules.
[[tool.uv.dependency-metadata]]
name = "veomni"
version = "0.1.11"
requires-dist = []
# Heavy native packages come prebuilt from the verl wheelhouse (cu130 / torch 2.11
# / cp312, built for x86_64 AND aarch64) — no source build here. megatron-bridge
# rides along (pure-python wheel off the same wheelhouse, so no git clone / build
# either), and being py3-none-any it is the one wheelhouse component with a single
# arch-independent build.
#
# The inference engines are deliberately NOT routed: upstream publishes cu130 /
# torch-2.11 wheels for the pinned vllm / sglang / sglang-kernel versions on PyPI,
# so the wheelhouse copies bought nothing (same builds, same download size, same
# install time) while costing a hash-less index entry. flashinfer stays on PyPI
# for the same reason — vllm/sglang pin their own and the wheelhouse's 0.6.13
# matches neither.
[tool.uv.sources]
apex = { index = "verl-wheelhouse" }
flash-attn = { index = "verl-wheelhouse" }
transformer-engine = { index = "verl-wheelhouse" }
megatron-bridge = { index = "verl-wheelhouse" }
# megatron-core / mbridge aren't in the wheelhouse; git-sourced (built at sync
# time). The wheelhouse megatron-bridge 0.5.2 (r0.5.0 branch) pairs with
# megatron-core core_v0.18.0 — bump the two together.
mbridge = { git = "https://github.com/ISEEKYAN/mbridge.git", rev = "641a5a0" }
megatron-core = { git = "https://github.com/NVIDIA/Megatron-LM.git", tag = "core_v0.18.0" }
# Per-extra routing to the matching pytorch wheel index.
torch = [
{ index = "pytorch-cu130", extra = "vllm" },
{ index = "pytorch-cu130", extra = "sglang" },
{ index = "pytorch-cu130", extra = "fsdp" },
{ index = "pytorch-cu130", extra = "megatron" },
{ index = "pytorch-cpu", extra = "cpu" },
# trtllm (DEFERRED) would route to CUDA 13 here:
# { index = "pytorch-cu130", extra = "trtllm" },
]
torchvision = [
{ index = "pytorch-cu130", extra = "vllm" },
{ index = "pytorch-cu130", extra = "sglang" },
{ index = "pytorch-cu130", extra = "fsdp" },
{ index = "pytorch-cu130", extra = "megatron" },
{ index = "pytorch-cpu", extra = "cpu" },
]
torchaudio = [
{ index = "pytorch-cu130", extra = "vllm" },
{ index = "pytorch-cu130", extra = "sglang" },
{ index = "pytorch-cu130", extra = "fsdp" },
{ index = "pytorch-cu130", extra = "megatron" },
{ index = "pytorch-cpu", extra = "cpu" },
]
[[tool.uv.index]]
name = "pytorch-cu130"
url = "https://download.pytorch.org/whl/cu130"
explicit = true
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
# verl wheelhouse: prebuilt cu130 / torch-2.11 / cp312 native wheels, published
# side by side for linux_x86_64 and linux_aarch64 (uv picks by platform tag).
# Only the packages routed above are pulled here (explicit = true); the rest stay
# on PyPI.
[[tool.uv.index]]
name = "verl-wheelhouse"
url = "https://verl-project.github.io/verl-wheelhouse/simple/"
explicit = true