Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
vllm_cscc
Commits
dbfe254e
Unverified
Commit
dbfe254e
authored
Jul 14, 2024
by
Ethan Xu
Committed by
GitHub
Jul 14, 2024
Browse files
[Feature] vLLM CLI (#5090)
Co-authored-by:
simon-mo
<
simon.mo@hey.com
>
parent
73030b7d
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
223 additions
and
36 deletions
+223
-36
benchmarks/benchmark_serving.py
benchmarks/benchmark_serving.py
+2
-2
docs/source/serving/openai_compatible_server.md
docs/source/serving/openai_compatible_server.md
+1
-1
setup.py
setup.py
+5
-0
tests/utils.py
tests/utils.py
+4
-2
vllm/entrypoints/openai/api_server.py
vllm/entrypoints/openai/api_server.py
+50
-28
vllm/entrypoints/openai/cli_args.py
vllm/entrypoints/openai/cli_args.py
+7
-3
vllm/scripts.py
vllm/scripts.py
+154
-0
No files found.
benchmarks/benchmark_serving.py
View file @
dbfe254e
...
@@ -2,8 +2,8 @@
...
@@ -2,8 +2,8 @@
On the server side, run one of the following commands:
On the server side, run one of the following commands:
vLLM OpenAI API server
vLLM OpenAI API server
python -m vllm.entrypoints.openai.api_server
\
vllm serve <your_model>
\
--model <your_model>
--swap-space 16
\
--swap-space 16
\
--disable-log-requests
--disable-log-requests
(TGI backend)
(TGI backend)
...
...
docs/source/serving/openai_compatible_server.md
View file @
dbfe254e
...
@@ -109,7 +109,7 @@ directory [here](https://github.com/vllm-project/vllm/tree/main/examples/)
...
@@ -109,7 +109,7 @@ directory [here](https://github.com/vllm-project/vllm/tree/main/examples/)
```
{argparse}
```
{argparse}
:module: vllm.entrypoints.openai.cli_args
:module: vllm.entrypoints.openai.cli_args
:func:
make_arg_parser
:func:
create_parser_for_docs
:prog: -m vllm.entrypoints.openai.api_server
:prog: -m vllm.entrypoints.openai.api_server
```
```
...
...
setup.py
View file @
dbfe254e
...
@@ -488,4 +488,9 @@ setup(
...
@@ -488,4 +488,9 @@ setup(
},
},
cmdclass
=
{
"build_ext"
:
cmake_build_ext
}
if
_build_custom_ops
()
else
{},
cmdclass
=
{
"build_ext"
:
cmake_build_ext
}
if
_build_custom_ops
()
else
{},
package_data
=
package_data
,
package_data
=
package_data
,
entry_points
=
{
"console_scripts"
:
[
"vllm=vllm.scripts:main"
,
],
},
)
)
tests/utils.py
View file @
dbfe254e
...
@@ -14,7 +14,7 @@ import requests
...
@@ -14,7 +14,7 @@ import requests
from
vllm.distributed
import
(
ensure_model_parallel_initialized
,
from
vllm.distributed
import
(
ensure_model_parallel_initialized
,
init_distributed_environment
)
init_distributed_environment
)
from
vllm.entrypoints.openai.cli_args
import
make_arg_parser
from
vllm.entrypoints.openai.cli_args
import
make_arg_parser
from
vllm.utils
import
get_open_port
,
is_hip
from
vllm.utils
import
FlexibleArgumentParser
,
get_open_port
,
is_hip
if
is_hip
():
if
is_hip
():
from
amdsmi
import
(
amdsmi_get_gpu_vram_usage
,
from
amdsmi
import
(
amdsmi_get_gpu_vram_usage
,
...
@@ -57,7 +57,9 @@ class RemoteOpenAIServer:
...
@@ -57,7 +57,9 @@ class RemoteOpenAIServer:
cli_args
=
cli_args
+
[
"--port"
,
str
(
get_open_port
())]
cli_args
=
cli_args
+
[
"--port"
,
str
(
get_open_port
())]
parser
=
make_arg_parser
()
parser
=
FlexibleArgumentParser
(
description
=
"vLLM's remote OpenAI server."
)
parser
=
make_arg_parser
(
parser
)
args
=
parser
.
parse_args
(
cli_args
)
args
=
parser
.
parse_args
(
cli_args
)
self
.
host
=
str
(
args
.
host
or
'localhost'
)
self
.
host
=
str
(
args
.
host
or
'localhost'
)
self
.
port
=
int
(
args
.
port
)
self
.
port
=
int
(
args
.
port
)
...
...
vllm/entrypoints/openai/api_server.py
View file @
dbfe254e
...
@@ -8,7 +8,7 @@ from typing import Optional, Set
...
@@ -8,7 +8,7 @@ from typing import Optional, Set
import
fastapi
import
fastapi
import
uvicorn
import
uvicorn
from
fastapi
import
Request
from
fastapi
import
APIRouter
,
Request
from
fastapi.exceptions
import
RequestValidationError
from
fastapi.exceptions
import
RequestValidationError
from
fastapi.middleware.cors
import
CORSMiddleware
from
fastapi.middleware.cors
import
CORSMiddleware
from
fastapi.responses
import
JSONResponse
,
Response
,
StreamingResponse
from
fastapi.responses
import
JSONResponse
,
Response
,
StreamingResponse
...
@@ -35,10 +35,14 @@ from vllm.entrypoints.openai.serving_completion import OpenAIServingCompletion
...
@@ -35,10 +35,14 @@ from vllm.entrypoints.openai.serving_completion import OpenAIServingCompletion
from
vllm.entrypoints.openai.serving_embedding
import
OpenAIServingEmbedding
from
vllm.entrypoints.openai.serving_embedding
import
OpenAIServingEmbedding
from
vllm.logger
import
init_logger
from
vllm.logger
import
init_logger
from
vllm.usage.usage_lib
import
UsageContext
from
vllm.usage.usage_lib
import
UsageContext
from
vllm.utils
import
FlexibleArgumentParser
from
vllm.version
import
__version__
as
VLLM_VERSION
from
vllm.version
import
__version__
as
VLLM_VERSION
TIMEOUT_KEEP_ALIVE
=
5
# seconds
TIMEOUT_KEEP_ALIVE
=
5
# seconds
logger
=
init_logger
(
__name__
)
engine
:
AsyncLLMEngine
engine_args
:
AsyncEngineArgs
openai_serving_chat
:
OpenAIServingChat
openai_serving_chat
:
OpenAIServingChat
openai_serving_completion
:
OpenAIServingCompletion
openai_serving_completion
:
OpenAIServingCompletion
openai_serving_embedding
:
OpenAIServingEmbedding
openai_serving_embedding
:
OpenAIServingEmbedding
...
@@ -64,35 +68,23 @@ async def lifespan(app: fastapi.FastAPI):
...
@@ -64,35 +68,23 @@ async def lifespan(app: fastapi.FastAPI):
yield
yield
app
=
fastapi
.
FastAPI
(
lifespan
=
lifespan
)
router
=
APIRouter
()
def
parse_args
():
parser
=
make_arg_parser
()
return
parser
.
parse_args
()
# Add prometheus asgi middleware to route /metrics requests
# Add prometheus asgi middleware to route /metrics requests
route
=
Mount
(
"/metrics"
,
make_asgi_app
())
route
=
Mount
(
"/metrics"
,
make_asgi_app
())
# Workaround for 307 Redirect for /metrics
# Workaround for 307 Redirect for /metrics
route
.
path_regex
=
re
.
compile
(
'^/metrics(?P<path>.*)$'
)
route
.
path_regex
=
re
.
compile
(
'^/metrics(?P<path>.*)$'
)
app
.
routes
.
append
(
route
)
router
.
routes
.
append
(
route
)
@
app
.
exception_handler
(
RequestValidationError
)
async
def
validation_exception_handler
(
_
,
exc
):
err
=
openai_serving_chat
.
create_error_response
(
message
=
str
(
exc
))
return
JSONResponse
(
err
.
model_dump
(),
status_code
=
HTTPStatus
.
BAD_REQUEST
)
@
app
.
get
(
"/health"
)
@
router
.
get
(
"/health"
)
async
def
health
()
->
Response
:
async
def
health
()
->
Response
:
"""Health check."""
"""Health check."""
await
openai_serving_chat
.
engine
.
check_health
()
await
openai_serving_chat
.
engine
.
check_health
()
return
Response
(
status_code
=
200
)
return
Response
(
status_code
=
200
)
@
app
.
post
(
"/tokenize"
)
@
router
.
post
(
"/tokenize"
)
async
def
tokenize
(
request
:
TokenizeRequest
):
async
def
tokenize
(
request
:
TokenizeRequest
):
generator
=
await
openai_serving_completion
.
create_tokenize
(
request
)
generator
=
await
openai_serving_completion
.
create_tokenize
(
request
)
if
isinstance
(
generator
,
ErrorResponse
):
if
isinstance
(
generator
,
ErrorResponse
):
...
@@ -103,7 +95,7 @@ async def tokenize(request: TokenizeRequest):
...
@@ -103,7 +95,7 @@ async def tokenize(request: TokenizeRequest):
return
JSONResponse
(
content
=
generator
.
model_dump
())
return
JSONResponse
(
content
=
generator
.
model_dump
())
@
app
.
post
(
"/detokenize"
)
@
router
.
post
(
"/detokenize"
)
async
def
detokenize
(
request
:
DetokenizeRequest
):
async
def
detokenize
(
request
:
DetokenizeRequest
):
generator
=
await
openai_serving_completion
.
create_detokenize
(
request
)
generator
=
await
openai_serving_completion
.
create_detokenize
(
request
)
if
isinstance
(
generator
,
ErrorResponse
):
if
isinstance
(
generator
,
ErrorResponse
):
...
@@ -114,19 +106,19 @@ async def detokenize(request: DetokenizeRequest):
...
@@ -114,19 +106,19 @@ async def detokenize(request: DetokenizeRequest):
return
JSONResponse
(
content
=
generator
.
model_dump
())
return
JSONResponse
(
content
=
generator
.
model_dump
())
@
app
.
get
(
"/v1/models"
)
@
router
.
get
(
"/v1/models"
)
async
def
show_available_models
():
async
def
show_available_models
():
models
=
await
openai_serving_completion
.
show_available_models
()
models
=
await
openai_serving_completion
.
show_available_models
()
return
JSONResponse
(
content
=
models
.
model_dump
())
return
JSONResponse
(
content
=
models
.
model_dump
())
@
app
.
get
(
"/version"
)
@
router
.
get
(
"/version"
)
async
def
show_version
():
async
def
show_version
():
ver
=
{
"version"
:
VLLM_VERSION
}
ver
=
{
"version"
:
VLLM_VERSION
}
return
JSONResponse
(
content
=
ver
)
return
JSONResponse
(
content
=
ver
)
@
app
.
post
(
"/v1/chat/completions"
)
@
router
.
post
(
"/v1/chat/completions"
)
async
def
create_chat_completion
(
request
:
ChatCompletionRequest
,
async
def
create_chat_completion
(
request
:
ChatCompletionRequest
,
raw_request
:
Request
):
raw_request
:
Request
):
generator
=
await
openai_serving_chat
.
create_chat_completion
(
generator
=
await
openai_serving_chat
.
create_chat_completion
(
...
@@ -142,7 +134,7 @@ async def create_chat_completion(request: ChatCompletionRequest,
...
@@ -142,7 +134,7 @@ async def create_chat_completion(request: ChatCompletionRequest,
return
JSONResponse
(
content
=
generator
.
model_dump
())
return
JSONResponse
(
content
=
generator
.
model_dump
())
@
app
.
post
(
"/v1/completions"
)
@
router
.
post
(
"/v1/completions"
)
async
def
create_completion
(
request
:
CompletionRequest
,
raw_request
:
Request
):
async
def
create_completion
(
request
:
CompletionRequest
,
raw_request
:
Request
):
generator
=
await
openai_serving_completion
.
create_completion
(
generator
=
await
openai_serving_completion
.
create_completion
(
request
,
raw_request
)
request
,
raw_request
)
...
@@ -156,7 +148,7 @@ async def create_completion(request: CompletionRequest, raw_request: Request):
...
@@ -156,7 +148,7 @@ async def create_completion(request: CompletionRequest, raw_request: Request):
return
JSONResponse
(
content
=
generator
.
model_dump
())
return
JSONResponse
(
content
=
generator
.
model_dump
())
@
app
.
post
(
"/v1/embeddings"
)
@
router
.
post
(
"/v1/embeddings"
)
async
def
create_embedding
(
request
:
EmbeddingRequest
,
raw_request
:
Request
):
async
def
create_embedding
(
request
:
EmbeddingRequest
,
raw_request
:
Request
):
generator
=
await
openai_serving_embedding
.
create_embedding
(
generator
=
await
openai_serving_embedding
.
create_embedding
(
request
,
raw_request
)
request
,
raw_request
)
...
@@ -167,8 +159,10 @@ async def create_embedding(request: EmbeddingRequest, raw_request: Request):
...
@@ -167,8 +159,10 @@ async def create_embedding(request: EmbeddingRequest, raw_request: Request):
return
JSONResponse
(
content
=
generator
.
model_dump
())
return
JSONResponse
(
content
=
generator
.
model_dump
())
if
__name__
==
"__main__"
:
def
build_app
(
args
):
args
=
parse_args
()
app
=
fastapi
.
FastAPI
(
lifespan
=
lifespan
)
app
.
include_router
(
router
)
app
.
root_path
=
args
.
root_path
app
.
add_middleware
(
app
.
add_middleware
(
CORSMiddleware
,
CORSMiddleware
,
...
@@ -178,6 +172,12 @@ if __name__ == "__main__":
...
@@ -178,6 +172,12 @@ if __name__ == "__main__":
allow_headers
=
args
.
allowed_headers
,
allow_headers
=
args
.
allowed_headers
,
)
)
@
app
.
exception_handler
(
RequestValidationError
)
async
def
validation_exception_handler
(
_
,
exc
):
err
=
openai_serving_chat
.
create_error_response
(
message
=
str
(
exc
))
return
JSONResponse
(
err
.
model_dump
(),
status_code
=
HTTPStatus
.
BAD_REQUEST
)
if
token
:
=
envs
.
VLLM_API_KEY
or
args
.
api_key
:
if
token
:
=
envs
.
VLLM_API_KEY
or
args
.
api_key
:
@
app
.
middleware
(
"http"
)
@
app
.
middleware
(
"http"
)
...
@@ -203,6 +203,12 @@ if __name__ == "__main__":
...
@@ -203,6 +203,12 @@ if __name__ == "__main__":
raise
ValueError
(
f
"Invalid middleware
{
middleware
}
. "
raise
ValueError
(
f
"Invalid middleware
{
middleware
}
. "
f
"Must be a function or a class."
)
f
"Must be a function or a class."
)
return
app
def
run_server
(
args
,
llm_engine
=
None
):
app
=
build_app
(
args
)
logger
.
info
(
"vLLM API server version %s"
,
VLLM_VERSION
)
logger
.
info
(
"vLLM API server version %s"
,
VLLM_VERSION
)
logger
.
info
(
"args: %s"
,
args
)
logger
.
info
(
"args: %s"
,
args
)
...
@@ -211,10 +217,12 @@ if __name__ == "__main__":
...
@@ -211,10 +217,12 @@ if __name__ == "__main__":
else
:
else
:
served_model_names
=
[
args
.
model
]
served_model_names
=
[
args
.
model
]
engine_args
=
AsyncEngineArgs
.
from_cli_args
(
args
)
global
engine
,
engine_args
engine
=
AsyncLLMEngine
.
from_engine_args
(
engine_args
=
AsyncEngineArgs
.
from_cli_args
(
args
)
engine_args
,
usage_context
=
UsageContext
.
OPENAI_API_SERVER
)
engine
=
(
llm_engine
if
llm_engine
is
not
None
else
AsyncLLMEngine
.
from_engine_args
(
engine_args
,
usage_context
=
UsageContext
.
OPENAI_API_SERVER
))
event_loop
:
Optional
[
asyncio
.
AbstractEventLoop
]
event_loop
:
Optional
[
asyncio
.
AbstractEventLoop
]
try
:
try
:
...
@@ -230,6 +238,10 @@ if __name__ == "__main__":
...
@@ -230,6 +238,10 @@ if __name__ == "__main__":
# When using single vLLM without engine_use_ray
# When using single vLLM without engine_use_ray
model_config
=
asyncio
.
run
(
engine
.
get_model_config
())
model_config
=
asyncio
.
run
(
engine
.
get_model_config
())
global
openai_serving_chat
global
openai_serving_completion
global
openai_serving_embedding
openai_serving_chat
=
OpenAIServingChat
(
engine
,
model_config
,
openai_serving_chat
=
OpenAIServingChat
(
engine
,
model_config
,
served_model_names
,
served_model_names
,
args
.
response_role
,
args
.
response_role
,
...
@@ -258,3 +270,13 @@ if __name__ == "__main__":
...
@@ -258,3 +270,13 @@ if __name__ == "__main__":
ssl_certfile
=
args
.
ssl_certfile
,
ssl_certfile
=
args
.
ssl_certfile
,
ssl_ca_certs
=
args
.
ssl_ca_certs
,
ssl_ca_certs
=
args
.
ssl_ca_certs
,
ssl_cert_reqs
=
args
.
ssl_cert_reqs
)
ssl_cert_reqs
=
args
.
ssl_cert_reqs
)
if
__name__
==
"__main__"
:
# NOTE(simon):
# This section should be in sync with vllm/scripts.py for CLI entrypoints.
parser
=
FlexibleArgumentParser
(
description
=
"vLLM OpenAI-Compatible RESTful API server."
)
parser
=
make_arg_parser
(
parser
)
args
=
parser
.
parse_args
()
run_server
(
args
)
vllm/entrypoints/openai/cli_args.py
View file @
dbfe254e
...
@@ -34,9 +34,7 @@ class PromptAdapterParserAction(argparse.Action):
...
@@ -34,9 +34,7 @@ class PromptAdapterParserAction(argparse.Action):
setattr
(
namespace
,
self
.
dest
,
adapter_list
)
setattr
(
namespace
,
self
.
dest
,
adapter_list
)
def
make_arg_parser
():
def
make_arg_parser
(
parser
:
FlexibleArgumentParser
)
->
FlexibleArgumentParser
:
parser
=
FlexibleArgumentParser
(
description
=
"vLLM OpenAI-Compatible RESTful API server."
)
parser
.
add_argument
(
"--host"
,
parser
.
add_argument
(
"--host"
,
type
=
nullable_str
,
type
=
nullable_str
,
default
=
None
,
default
=
None
,
...
@@ -133,3 +131,9 @@ def make_arg_parser():
...
@@ -133,3 +131,9 @@ def make_arg_parser():
parser
=
AsyncEngineArgs
.
add_cli_args
(
parser
)
parser
=
AsyncEngineArgs
.
add_cli_args
(
parser
)
return
parser
return
parser
def
create_parser_for_docs
()
->
FlexibleArgumentParser
:
parser_for_docs
=
FlexibleArgumentParser
(
prog
=
"-m vllm.entrypoints.openai.api_server"
)
return
make_arg_parser
(
parser_for_docs
)
vllm/scripts.py
0 → 100644
View file @
dbfe254e
# The CLI entrypoint to vLLM.
import
argparse
import
os
import
signal
import
sys
from
typing
import
Optional
from
openai
import
OpenAI
from
vllm.entrypoints.openai.api_server
import
run_server
from
vllm.entrypoints.openai.cli_args
import
make_arg_parser
from
vllm.utils
import
FlexibleArgumentParser
def
registrer_signal_handlers
():
def
signal_handler
(
sig
,
frame
):
sys
.
exit
(
0
)
signal
.
signal
(
signal
.
SIGINT
,
signal_handler
)
signal
.
signal
(
signal
.
SIGTSTP
,
signal_handler
)
def
serve
(
args
:
argparse
.
Namespace
)
->
None
:
# EngineArgs expects the model name to be passed as --model.
args
.
model
=
args
.
model_tag
run_server
(
args
)
def
interactive_cli
(
args
:
argparse
.
Namespace
)
->
None
:
registrer_signal_handlers
()
base_url
=
args
.
url
api_key
=
args
.
api_key
or
os
.
environ
.
get
(
"OPENAI_API_KEY"
,
"EMPTY"
)
openai_client
=
OpenAI
(
api_key
=
api_key
,
base_url
=
base_url
)
if
args
.
model_name
:
model_name
=
args
.
model_name
else
:
available_models
=
openai_client
.
models
.
list
()
model_name
=
available_models
.
data
[
0
].
id
print
(
f
"Using model:
{
model_name
}
"
)
if
args
.
command
==
"complete"
:
complete
(
model_name
,
openai_client
)
elif
args
.
command
==
"chat"
:
chat
(
args
.
system_prompt
,
model_name
,
openai_client
)
def
complete
(
model_name
:
str
,
client
:
OpenAI
)
->
None
:
print
(
"Please enter prompt to complete:"
)
while
True
:
input_prompt
=
input
(
"> "
)
completion
=
client
.
completions
.
create
(
model
=
model_name
,
prompt
=
input_prompt
)
output
=
completion
.
choices
[
0
].
text
print
(
output
)
def
chat
(
system_prompt
:
Optional
[
str
],
model_name
:
str
,
client
:
OpenAI
)
->
None
:
conversation
=
[]
if
system_prompt
is
not
None
:
conversation
.
append
({
"role"
:
"system"
,
"content"
:
system_prompt
})
print
(
"Please enter a message for the chat model:"
)
while
True
:
input_message
=
input
(
"> "
)
message
=
{
"role"
:
"user"
,
"content"
:
input_message
}
conversation
.
append
(
message
)
chat_completion
=
client
.
chat
.
completions
.
create
(
model
=
model_name
,
messages
=
conversation
)
response_message
=
chat_completion
.
choices
[
0
].
message
output
=
response_message
.
content
conversation
.
append
(
response_message
)
print
(
output
)
def
_add_query_options
(
parser
:
FlexibleArgumentParser
)
->
FlexibleArgumentParser
:
parser
.
add_argument
(
"--url"
,
type
=
str
,
default
=
"http://localhost:8000/v1"
,
help
=
"url of the running OpenAI-Compatible RESTful API server"
)
parser
.
add_argument
(
"--model-name"
,
type
=
str
,
default
=
None
,
help
=
(
"The model name used in prompt completion, default to "
"the first model in list models API call."
))
parser
.
add_argument
(
"--api-key"
,
type
=
str
,
default
=
None
,
help
=
(
"API key for OpenAI services. If provided, this api key "
"will overwrite the api key obtained through environment variables."
))
return
parser
def
main
():
parser
=
FlexibleArgumentParser
(
description
=
"vLLM CLI"
)
subparsers
=
parser
.
add_subparsers
(
required
=
True
)
serve_parser
=
subparsers
.
add_parser
(
"serve"
,
help
=
"Start the vLLM OpenAI Compatible API server"
,
usage
=
"vllm serve <model_tag> [options]"
)
serve_parser
.
add_argument
(
"model_tag"
,
type
=
str
,
help
=
"The model tag to serve"
)
serve_parser
=
make_arg_parser
(
serve_parser
)
serve_parser
.
set_defaults
(
dispatch_function
=
serve
)
complete_parser
=
subparsers
.
add_parser
(
"complete"
,
help
=
(
"Generate text completions based on the given prompt "
"via the running API server"
),
usage
=
"vllm complete [options]"
)
_add_query_options
(
complete_parser
)
complete_parser
.
set_defaults
(
dispatch_function
=
interactive_cli
,
command
=
"complete"
)
chat_parser
=
subparsers
.
add_parser
(
"chat"
,
help
=
"Generate chat completions via the running API server"
,
usage
=
"vllm chat [options]"
)
_add_query_options
(
chat_parser
)
chat_parser
.
add_argument
(
"--system-prompt"
,
type
=
str
,
default
=
None
,
help
=
(
"The system prompt to be added to the chat template, "
"used for models that support system prompts."
))
chat_parser
.
set_defaults
(
dispatch_function
=
interactive_cli
,
command
=
"chat"
)
args
=
parser
.
parse_args
()
# One of the sub commands should be executed.
if
hasattr
(
args
,
"dispatch_function"
):
args
.
dispatch_function
(
args
)
else
:
parser
.
print_help
()
if
__name__
==
"__main__"
:
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment