Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,1389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import gc
|
| 3 |
+
import json
|
| 4 |
+
import random
|
| 5 |
+
import torch
|
| 6 |
+
import asyncio
|
| 7 |
+
import logging
|
| 8 |
+
import time
|
| 9 |
+
import queue
|
| 10 |
+
import sys
|
| 11 |
+
from typing import List, Dict, Any, Optional, Union, AsyncGenerator, Tuple
|
| 12 |
+
from fastapi import FastAPI, HTTPException, Query, Request, Depends, status
|
| 13 |
+
from fastapi.responses import StreamingResponse, PlainTextResponse, HTMLResponse, JSONResponse
|
| 14 |
+
from fastapi.security import APIKeyHeader
|
| 15 |
+
from pydantic import BaseModel, Field, ValidationError, validator
|
| 16 |
+
from transformers import (
|
| 17 |
+
AutoConfig, AutoModelForCausalLM, AutoTokenizer,
|
| 18 |
+
GenerationConfig, LogitsProcessorList,
|
| 19 |
+
MinLengthLogitsProcessor, MaxLengthCriteria,
|
| 20 |
+
StoppingCriteriaList, StoppingCriteria,
|
| 21 |
+
TextStreamer,
|
| 22 |
+
)
|
| 23 |
+
try:
|
| 24 |
+
from transformers.generation.utils import GenerationOutput
|
| 25 |
+
except ImportError:
|
| 26 |
+
GenerationOutput = Any
|
| 27 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 28 |
+
import uvicorn
|
| 29 |
+
import math
|
| 30 |
+
import torch.nn.functional as F
|
| 31 |
+
import copy
|
| 32 |
+
|
| 33 |
+
class SyncToAsyncQueue:
|
| 34 |
+
def __init__(self):
|
| 35 |
+
self._queue = queue.Queue()
|
| 36 |
+
try:
|
| 37 |
+
self._loop = asyncio.get_event_loop()
|
| 38 |
+
except RuntimeError:
|
| 39 |
+
self._loop = asyncio.new_event_loop()
|
| 40 |
+
asyncio.set_event_loop(self._loop)
|
| 41 |
+
self._get_waiters = []
|
| 42 |
+
self._closed = False
|
| 43 |
+
|
| 44 |
+
def put_nowait(self, item):
|
| 45 |
+
if self._closed:
|
| 46 |
+
raise asyncio.QueueClosed("Queue is closed")
|
| 47 |
+
self._queue.put_nowait(item)
|
| 48 |
+
while self._get_waiters:
|
| 49 |
+
waiter = self._get_waiters.pop(0)
|
| 50 |
+
if not waiter.done():
|
| 51 |
+
waiter.set_result(None)
|
| 52 |
+
|
| 53 |
+
async def get(self):
|
| 54 |
+
if self._closed and self._queue.empty():
|
| 55 |
+
raise asyncio.QueueClosed("Queue is closed")
|
| 56 |
+
if not self._queue.empty():
|
| 57 |
+
return self._queue.get_nowait()
|
| 58 |
+
waiter = self._loop.create_future()
|
| 59 |
+
self._get_waiters.append(waiter)
|
| 60 |
+
try:
|
| 61 |
+
await waiter
|
| 62 |
+
except asyncio.CancelledError:
|
| 63 |
+
if waiter in self._get_waiters:
|
| 64 |
+
self._get_waiters.remove(waiter)
|
| 65 |
+
raise
|
| 66 |
+
if self._closed and self._queue.empty():
|
| 67 |
+
raise asyncio.QueueClosed("Queue is closed")
|
| 68 |
+
if not self._queue.empty():
|
| 69 |
+
return self._queue.get_nowait()
|
| 70 |
+
raise asyncio.QueueClosed("Queue is closed unexpectedly after waiter finished")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def close(self):
|
| 74 |
+
self._closed = True
|
| 75 |
+
while self._get_waiters:
|
| 76 |
+
waiter = self._get_waiters.pop(0)
|
| 77 |
+
if not waiter.done():
|
| 78 |
+
waiter.set_exception(asyncio.QueueClosed("Queue is closed"))
|
| 79 |
+
|
| 80 |
+
class QueueTextStreamer(TextStreamer):
|
| 81 |
+
def __init__(self, queue: SyncToAsyncQueue, tokenizer, skip_prompt: bool = False, **decode_kwargs):
|
| 82 |
+
class QueueWriter:
|
| 83 |
+
def __init__(self, queue):
|
| 84 |
+
self._queue = queue
|
| 85 |
+
|
| 86 |
+
def write(self, text):
|
| 87 |
+
if text:
|
| 88 |
+
try:
|
| 89 |
+
self._queue.put_nowait({"type": "token", "text": text})
|
| 90 |
+
except asyncio.QueueClosed:
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
def flush(self):
|
| 94 |
+
pass
|
| 95 |
+
|
| 96 |
+
def isatty(self): return False
|
| 97 |
+
def writable(self): return True
|
| 98 |
+
|
| 99 |
+
self.queue = queue
|
| 100 |
+
self.queue_writer = QueueWriter(queue)
|
| 101 |
+
super().__init__(tokenizer, self.queue_writer, skip_prompt=skip_prompt, **decode_kwargs)
|
| 102 |
+
|
| 103 |
+
def on_finalized_text(self, text: str, stream_end: bool = False):
|
| 104 |
+
if text:
|
| 105 |
+
try:
|
| 106 |
+
self.queue.put_nowait({"type": "token", "text": text})
|
| 107 |
+
except asyncio.QueueClosed:
|
| 108 |
+
pass
|
| 109 |
+
if stream_end:
|
| 110 |
+
try:
|
| 111 |
+
self.queue.put_nowait({"type": "stream_end_signal"})
|
| 112 |
+
except asyncio.QueueClosed:
|
| 113 |
+
pass
|
| 114 |
+
|
| 115 |
+
def __call__(self, token_ids, scores=None, **kwargs):
|
| 116 |
+
super().__call__(token_ids, scores, **kwargs)
|
| 117 |
+
|
| 118 |
+
class StopSequenceCriteria(StoppingCriteria):
|
| 119 |
+
def __init__(self, stop_sequences: List[str], tokenizer: AutoTokenizer):
|
| 120 |
+
self.tokenizer = tokenizer
|
| 121 |
+
self.stop_sequences_text = []
|
| 122 |
+
self.stop_sequence_ids = []
|
| 123 |
+
for seq in stop_sequences:
|
| 124 |
+
if seq:
|
| 125 |
+
encoded_ids = tokenizer.encode(seq, add_special_tokens=False)
|
| 126 |
+
decoded_text = tokenizer.decode(encoded_ids, skip_special_tokens=True)
|
| 127 |
+
if decoded_text:
|
| 128 |
+
self.stop_sequences_text.append(decoded_text)
|
| 129 |
+
self.stop_sequence_ids.append(encoded_ids)
|
| 130 |
+
|
| 131 |
+
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
|
| 132 |
+
if not self.stop_sequence_ids and not self.stop_sequences_text:
|
| 133 |
+
return False
|
| 134 |
+
input_ids_list = input_ids[0].tolist()
|
| 135 |
+
for stop_seq_ids in self.stop_sequence_ids:
|
| 136 |
+
stop_len = len(stop_seq_ids)
|
| 137 |
+
if len(input_ids_list) >= stop_len:
|
| 138 |
+
if input_ids_list[-stop_len:] == stop_seq_ids:
|
| 139 |
+
return True
|
| 140 |
+
check_tail_len = max(len(ids) for ids in self.stop_sequence_ids) + 10 if self.stop_sequence_ids else 50
|
| 141 |
+
tail_ids = input_ids_list[-min(check_tail_len, len(input_ids_list)):]
|
| 142 |
+
tail_text = self.tokenizer.decode(tail_ids, skip_special_tokens=True)
|
| 143 |
+
for stop_seq_text in self.stop_sequences_text:
|
| 144 |
+
if stop_seq_text and tail_text.endswith(stop_seq_text):
|
| 145 |
+
return True
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
logging.getLogger("uvicorn").handlers.clear()
|
| 149 |
+
logging.getLogger("uvicorn.error").handlers.clear()
|
| 150 |
+
logging.getLogger("uvicorn.access").handlers.clear()
|
| 151 |
+
logging.getLogger("uvicorn").propagate = False
|
| 152 |
+
logging.getLogger("uvicorn.error").propagate = False
|
| 153 |
+
logging.getLogger("uvicorn.access").propagate = False
|
| 154 |
+
logging.getLogger("uvicorn").setLevel(logging.CRITICAL)
|
| 155 |
+
logging.getLogger("uvicorn.error").setLevel(logging.CRITICAL)
|
| 156 |
+
logging.getLogger("uvicorn.access").setLevel(logging.CRITICAL)
|
| 157 |
+
logging.getLogger("fastapi").setLevel(logging.CRITICAL)
|
| 158 |
+
logging.getLogger("transformers").setLevel(logging.CRITICAL)
|
| 159 |
+
logging.getLogger().handlers.clear()
|
| 160 |
+
logging.getLogger().addHandler(logging.NullHandler())
|
| 161 |
+
|
| 162 |
+
API_KEY_NAME = "X-API-Key"
|
| 163 |
+
api_key_header = APIKeyHeader(name=API_KEY_NAME)
|
| 164 |
+
API_KEY = os.environ.get("API_KEY", "YOUR_SECRET_API_KEY")
|
| 165 |
+
|
| 166 |
+
MODEL_NAME = os.environ.get("MODEL_NAME", "jnjj/multi-dataset-model")
|
| 167 |
+
SYSTEM_PROMPT = os.environ.get("SYSTEM_PROMPT", "Eres un asistente profesional y servicial.")
|
| 168 |
+
MAX_CONTEXT_TOKENS = int(os.environ.get("MAX_CONTEXT_TOKENS", 1024))
|
| 169 |
+
MAX_GENERATION_TOKENS = int(os.environ.get("MAX_GENERATION_TOKENS", 512))
|
| 170 |
+
MAX_CONCURRENT_GENERATIONS = int(os.environ.get("MAX_CONCURRENT_GENERATIONS", 4))
|
| 171 |
+
TRUST_REMOTE_CODE = os.environ.get("TRUST_REMOTE_CODE", "false").lower() == "true"
|
| 172 |
+
ENABLE_FLASH_ATTENTION_2 = os.environ.get("ENABLE_FLASH_ATTENTION_2", "false").lower() == "true"
|
| 173 |
+
TORCH_DTYPE_STR = os.environ.get("TORCH_DTYPE", "float16")
|
| 174 |
+
TORCH_DTYPE = getattr(torch, TORCH_DTYPE_STR.lower(), torch.float16)
|
| 175 |
+
if TORCH_DTYPE_STR.lower() == "bfloat16" and (not torch.cuda.is_available() or (torch.cuda.is_available() and not torch.cuda.get_device_properties(0).has_bfloat16)):
|
| 176 |
+
TORCH_DTYPE = torch.float16
|
| 177 |
+
elif not isinstance(TORCH_DTYPE, torch.dtype):
|
| 178 |
+
TORCH_DTYPE = torch.float16
|
| 179 |
+
|
| 180 |
+
global_model = None
|
| 181 |
+
global_tokenizer = None
|
| 182 |
+
global_tokens: Dict[str, Optional[int]] = {}
|
| 183 |
+
executor = ThreadPoolExecutor(max_workers=MAX_CONCURRENT_GENERATIONS)
|
| 184 |
+
generation_semaphore = asyncio.Semaphore(MAX_CONCURRENT_GENERATIONS)
|
| 185 |
+
|
| 186 |
+
REQUEST_RATE_LIMIT = int(os.environ.get("REQUEST_RATE_LIMIT", 60))
|
| 187 |
+
RATE_LIMIT_WINDOW_SECONDS = int(os.environ.get("RATE_LIMIT_WINDOW_SECONDS", 60))
|
| 188 |
+
request_counts: Dict[str, int] = {}
|
| 189 |
+
|
| 190 |
+
async def rate_limit_reset_task():
|
| 191 |
+
while True:
|
| 192 |
+
await asyncio.sleep(RATE_LIMIT_WINDOW_SECONDS)
|
| 193 |
+
request_counts.clear()
|
| 194 |
+
|
| 195 |
+
class GenerateRequest(BaseModel):
|
| 196 |
+
input_text: str
|
| 197 |
+
history: Optional[List[Dict[str, str]]] = None
|
| 198 |
+
stream: bool = True
|
| 199 |
+
temperature: float = Field(1.0, ge=0.0, le=2.0)
|
| 200 |
+
top_k: int = Field(50, ge=0)
|
| 201 |
+
top_p: float = Field(1.0, ge=0.0, le=1.0)
|
| 202 |
+
repetition_penalty: float = Field(1.0, ge=0.0)
|
| 203 |
+
frequency_penalty: float = Field(0.0, ge=0.0)
|
| 204 |
+
presence_penalty: float = Field(0.0, ge=0.0)
|
| 205 |
+
num_beams: int = Field(1, ge=1)
|
| 206 |
+
length_penalty: float = Field(1.0, ge=0.0)
|
| 207 |
+
no_repeat_ngram_size: int = Field(0, ge=0)
|
| 208 |
+
early_stopping: bool = False
|
| 209 |
+
do_sample: bool = True
|
| 210 |
+
use_mirostat: bool = False
|
| 211 |
+
mirostat_tau: float = Field(5.0, ge=0.0)
|
| 212 |
+
mirostat_eta: float = Field(0.1, ge=0.0)
|
| 213 |
+
max_new_tokens: int = Field(MAX_GENERATION_TOKENS, ge=1)
|
| 214 |
+
system_prompt: Optional[str] = None
|
| 215 |
+
seed: Optional[int] = None
|
| 216 |
+
stop_sequences: Optional[List[str]] = None
|
| 217 |
+
tokenize_only: bool = False
|
| 218 |
+
strip_trailing_whitespace: bool = False
|
| 219 |
+
remove_incomplete_sentences: bool = False
|
| 220 |
+
num_return_sequences: int = Field(1, ge=1, le=5)
|
| 221 |
+
bad_words_ids: Optional[List[List[int]]] = None
|
| 222 |
+
forced_bos_token_id: Optional[int] = None
|
| 223 |
+
forced_eos_token_id: Optional[int] = None
|
| 224 |
+
renormalize_logits: Optional[bool] = None
|
| 225 |
+
suppress_tokens: Optional[List[int]] = None
|
| 226 |
+
begin_suppress_tokens: Optional[List[int]] = None
|
| 227 |
+
end_suppress_tokens: Optional[List[int]] = None
|
| 228 |
+
encoder_no_repeat_ngram_size: int = Field(0, ge=0)
|
| 229 |
+
min_length: int = Field(0, ge=0)
|
| 230 |
+
max_length: Optional[int] = None
|
| 231 |
+
exponential_decay_length_penalty: Optional[Tuple[float, int, float]] = None
|
| 232 |
+
use_cache: bool = True
|
| 233 |
+
top_k_dynamic: Optional[int] = None
|
| 234 |
+
top_p_dynamic: Optional[float] = None
|
| 235 |
+
typical_p: float = Field(1.0, ge=0.0, le=1.0)
|
| 236 |
+
epsilon_cutoff: float = Field(0.0, ge=0.0)
|
| 237 |
+
eta_cutoff: float = Field(0.0, ge=0.0)
|
| 238 |
+
temperature_cutoff: Optional[float] = Field(None, ge=0.0)
|
| 239 |
+
encoder_repetition_penalty: float = Field(1.0, ge=0.0)
|
| 240 |
+
max_time: Optional[float] = Field(None, ge=0.0)
|
| 241 |
+
output_watermark: bool = False
|
| 242 |
+
remove_input_from_output: bool = False
|
| 243 |
+
eos_token_id_override: Optional[int] = None
|
| 244 |
+
pad_token_id_override: Optional[int] = None
|
| 245 |
+
bos_token_id_override: Optional[int] = None
|
| 246 |
+
repetition_penalty_range: Optional[int] = Field(None, ge=0)
|
| 247 |
+
diversity_penalty: float = Field(0.0, ge=0.0)
|
| 248 |
+
num_beam_groups: int = Field(1, ge=1)
|
| 249 |
+
return_dict_in_generate: bool = False
|
| 250 |
+
output_attentions: bool = False
|
| 251 |
+
output_hidden_states: bool = False
|
| 252 |
+
output_scores: bool = False
|
| 253 |
+
return_token_logprobs: bool = False
|
| 254 |
+
return_text_from_sequence: bool = True
|
| 255 |
+
length_normalization_factor: Optional[float] = None
|
| 256 |
+
min_new_tokens: int = Field(0, ge=0)
|
| 257 |
+
do_normalize_logits: bool = False
|
| 258 |
+
return_generation_inputs: bool = False
|
| 259 |
+
return_unused_generate_parameters: bool = False
|
| 260 |
+
use_fast_tokenizer: Optional[bool] = None
|
| 261 |
+
model_kwargs: Optional[Dict[str, Any]] = None
|
| 262 |
+
tokenizer_kwargs: Optional[Dict[str, Any]] = None
|
| 263 |
+
generation_kwargs: Optional[Dict[str, Any]] = None
|
| 264 |
+
prompt_lookup_num_tokens: Optional[int] = Field(None, ge=0)
|
| 265 |
+
tfs_lambda: Optional[float] = Field(None, ge=0.0, le=1.0)
|
| 266 |
+
top_p_min: Optional[float] = Field(None, ge=0.0, le=1.0)
|
| 267 |
+
typical_p_min: Optional[float] = Field(None, ge=0.0, le=1.0)
|
| 268 |
+
sequence_bias: Optional[Dict[int, float]] = None
|
| 269 |
+
negative_prompt_ids: Optional[List[int]] = None
|
| 270 |
+
return_only_text: bool = False
|
| 271 |
+
|
| 272 |
+
@validator('stop_sequences')
|
| 273 |
+
def validate_stop_sequences(cls, v):
|
| 274 |
+
if v is not None:
|
| 275 |
+
if not all(isinstance(seq, str) for seq in v):
|
| 276 |
+
raise ValueError('Each stop sequence must be a string')
|
| 277 |
+
return v
|
| 278 |
+
|
| 279 |
+
@validator('bad_words_ids')
|
| 280 |
+
def validate_bad_words_ids(cls, v):
|
| 281 |
+
if v is not None:
|
| 282 |
+
if not all(isinstance(word_id_list, list) and all(isinstance(token_id, int) for token_id in word_id_list) for word_id_list in v):
|
| 283 |
+
raise ValueError('bad_words_ids must be a list of lists of integers')
|
| 284 |
+
return v
|
| 285 |
+
|
| 286 |
+
@validator('exponential_decay_length_penalty')
|
| 287 |
+
def validate_exponential_decay_length_penalty(cls, v):
|
| 288 |
+
if v is not None:
|
| 289 |
+
if not (isinstance(v, (list, tuple)) and len(v) == 3 and
|
| 290 |
+
isinstance(v[0], (int, float)) and v[0] > 0 and
|
| 291 |
+
isinstance(v[1], int) and v[1] >= 0 and
|
| 292 |
+
isinstance(v[2], (int, float))):
|
| 293 |
+
raise ValueError('exponential_decay_length_penalty must be a tuple/list of 3 numbers (decay_factor, start_index, threshold)')
|
| 294 |
+
return v
|
| 295 |
+
|
| 296 |
+
@validator('sequence_bias')
|
| 297 |
+
def validate_sequence_bias(cls, v):
|
| 298 |
+
if v is not None:
|
| 299 |
+
if not all(isinstance(k, int) and isinstance(val, float) for k, val in v.items()):
|
| 300 |
+
raise ValueError('sequence_bias must be a dictionary with integer keys and float values')
|
| 301 |
+
return v
|
| 302 |
+
|
| 303 |
+
@validator('negative_prompt_ids')
|
| 304 |
+
def validate_negative_prompt_ids(cls, v):
|
| 305 |
+
if v is not None:
|
| 306 |
+
if not all(isinstance(token_id, int) for token_id in v):
|
| 307 |
+
raise ValueError('negative_prompt_ids must be a list of integers')
|
| 308 |
+
return v
|
| 309 |
+
|
| 310 |
+
class TokenizeRequest(BaseModel):
|
| 311 |
+
text: Union[str, List[str]]
|
| 312 |
+
add_special_tokens: bool = True
|
| 313 |
+
is_split_into_words: bool = False
|
| 314 |
+
return_token_type_ids: bool = False
|
| 315 |
+
padding: Union[bool, str] = False
|
| 316 |
+
truncation: Union[bool, str] = False
|
| 317 |
+
max_length: Optional[int] = None
|
| 318 |
+
return_tensors: Optional[str] = None
|
| 319 |
+
return_attention_mask: Optional[bool] = None
|
| 320 |
+
return_offsets_mapping: Optional[bool] = None
|
| 321 |
+
return_length: Optional[bool] = None
|
| 322 |
+
verbose: bool = False
|
| 323 |
+
tokenizer_kwargs: Optional[Dict[str, Any]] = None
|
| 324 |
+
use_fast_tokenizer: Optional[bool] = None
|
| 325 |
+
chat_format: bool = False
|
| 326 |
+
return_word_ids: bool = False
|
| 327 |
+
|
| 328 |
+
class DecodeRequest(BaseModel):
|
| 329 |
+
token_ids: List[int]
|
| 330 |
+
skip_special_tokens: bool = True
|
| 331 |
+
clean_up_tokenization_spaces: bool = True
|
| 332 |
+
decode_kwargs: Optional[Dict[str, Any]] = None
|
| 333 |
+
|
| 334 |
+
class SystemPromptUpdateRequest(BaseModel):
|
| 335 |
+
system_prompt: str
|
| 336 |
+
|
| 337 |
+
class ModelReloadRequest(BaseModel):
|
| 338 |
+
model_name: Optional[str] = None
|
| 339 |
+
trust_remote_code: Optional[bool] = None
|
| 340 |
+
enable_flash_attention_2: Optional[bool] = None
|
| 341 |
+
torch_dtype: Optional[str] = None
|
| 342 |
+
model_kwargs: Optional[Dict[str, Any]] = None
|
| 343 |
+
tokenizer_kwargs: Optional[Dict[str, Any]] = None
|
| 344 |
+
|
| 345 |
+
def authenticate_api_key(api_key: str = Depends(api_key_header)):
|
| 346 |
+
if api_key != API_KEY:
|
| 347 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API Key")
|
| 348 |
+
return api_key
|
| 349 |
+
|
| 350 |
+
def format_conversation(input_text: str, history: Optional[List[Dict[str, str]]], system_prompt: Optional[str], tokenizer: AutoTokenizer) -> str:
|
| 351 |
+
full_history: List[Dict[str, str]] = []
|
| 352 |
+
used_system_prompt = system_prompt if system_prompt is not None else SYSTEM_PROMPT
|
| 353 |
+
if used_system_prompt:
|
| 354 |
+
full_history.append({"role": "system", "content": used_system_prompt})
|
| 355 |
+
if history:
|
| 356 |
+
full_history.extend(history)
|
| 357 |
+
full_history.append({"role": "user", "content": input_text})
|
| 358 |
+
if tokenizer and hasattr(tokenizer, 'apply_chat_template') and tokenizer.chat_template:
|
| 359 |
+
try:
|
| 360 |
+
return tokenizer.apply_chat_template(full_history, tokenize=False, add_generation_prompt=True)
|
| 361 |
+
except Exception:
|
| 362 |
+
pass
|
| 363 |
+
formatted_text = ""
|
| 364 |
+
for message in full_history:
|
| 365 |
+
if message["role"] == "system":
|
| 366 |
+
formatted_text += f"{message['content'].strip()}\n\n"
|
| 367 |
+
elif message["role"] == "user":
|
| 368 |
+
formatted_text += f"Usuario: {message['content'].strip()}\n"
|
| 369 |
+
elif message["role"] == "assistant":
|
| 370 |
+
formatted_text += f"Bot: {message['content'].strip()}\n"
|
| 371 |
+
if not formatted_text.strip().endswith("Bot:"):
|
| 372 |
+
formatted_text = formatted_text.strip() + "\nBot:"
|
| 373 |
+
return formatted_text.strip()
|
| 374 |
+
|
| 375 |
+
def truncate_encoded_ids(input_ids: torch.Tensor, max_length: int) -> torch.Tensor:
|
| 376 |
+
if input_ids.shape[-1] > max_length:
|
| 377 |
+
return input_ids[:, -max_length:]
|
| 378 |
+
return input_ids
|
| 379 |
+
|
| 380 |
+
def apply_seed(seed: Optional[int]):
|
| 381 |
+
if seed is not None:
|
| 382 |
+
torch.manual_seed(seed)
|
| 383 |
+
if torch.cuda.is_available():
|
| 384 |
+
torch.cuda.manual_seed_all(seed)
|
| 385 |
+
random.seed(seed)
|
| 386 |
+
|
| 387 |
+
def get_generation_params(req: GenerateRequest, initial_ids: torch.Tensor, tokenizer: AutoTokenizer, device: str) -> Tuple[GenerationConfig, StoppingCriteriaList, LogitsProcessorList, Dict[str, Any]]:
|
| 388 |
+
initial_len = initial_ids.shape[-1]
|
| 389 |
+
logits_processor = LogitsProcessorList()
|
| 390 |
+
if req.min_length is not None and req.min_length > 0:
|
| 391 |
+
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id", -1)
|
| 392 |
+
logits_processor.append(MinLengthLogitsProcessor(initial_len + req.min_length, eos_token_id))
|
| 393 |
+
|
| 394 |
+
stopping_criteria = StoppingCriteriaList()
|
| 395 |
+
max_gen_len = req.max_new_tokens if req.max_new_tokens is not None else MAX_GENERATION_TOKENS
|
| 396 |
+
effective_max_length = req.max_length if req.max_length is not None and req.max_length > 0 else initial_len + max_gen_len
|
| 397 |
+
if effective_max_length > 0:
|
| 398 |
+
stopping_criteria.append(MaxLengthCriteria(effective_max_length))
|
| 399 |
+
|
| 400 |
+
if req.stop_sequences:
|
| 401 |
+
stop_sequence_criteria_instance = StopSequenceCriteria(req.stop_sequences, tokenizer)
|
| 402 |
+
if stop_sequence_criteria_instance.stop_sequence_ids or stop_sequence_criteria_instance.stop_sequences_text:
|
| 403 |
+
stopping_criteria.append(stop_sequence_criteria_instance)
|
| 404 |
+
|
| 405 |
+
gen_cfg_params = {
|
| 406 |
+
"temperature": req.temperature,
|
| 407 |
+
"top_k": req.top_k,
|
| 408 |
+
"top_p": req.top_p,
|
| 409 |
+
"repetition_penalty": req.repetition_penalty,
|
| 410 |
+
"num_beams": req.num_beams if not req.stream else 1,
|
| 411 |
+
"length_penalty": req.length_penalty,
|
| 412 |
+
"no_repeat_ngram_size": req.no_repeat_ngram_size,
|
| 413 |
+
"early_stopping": req.early_stopping,
|
| 414 |
+
"do_sample": req.do_sample,
|
| 415 |
+
"max_new_tokens": req.max_new_tokens,
|
| 416 |
+
"eos_token_id": req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id"),
|
| 417 |
+
"pad_token_id": req.pad_token_id_override if req.pad_token_id_override is not None else global_tokens.get("pad_token_id"),
|
| 418 |
+
"bos_token_id": req.bos_token_id_override if req.bos_token_id_override is not None else (global_tokenizer.bos_token_id if global_tokenizer else None),
|
| 419 |
+
"num_return_sequences": req.num_return_sequences if not req.stream else 1,
|
| 420 |
+
"forced_bos_token_id": req.forced_bos_token_id,
|
| 421 |
+
"forced_eos_token_id": req.forced_eos_token_id,
|
| 422 |
+
"encoder_no_repeat_ngram_size": req.encoder_no_repeat_ngram_size,
|
| 423 |
+
"min_length": req.min_length,
|
| 424 |
+
"max_length": effective_max_length,
|
| 425 |
+
"exponential_decay_length_penalty": req.exponential_decay_length_penalty,
|
| 426 |
+
"use_cache": req.use_cache,
|
| 427 |
+
"top_k_dynamic": req.top_k_dynamic,
|
| 428 |
+
"top_p_dynamic": req.top_p_dynamic,
|
| 429 |
+
"typical_p": req.typical_p,
|
| 430 |
+
"epsilon_cutoff": req.epsilon_cutoff,
|
| 431 |
+
"eta_cutoff": req.eta_cutoff,
|
| 432 |
+
"temperature_cutoff": req.temperature_cutoff,
|
| 433 |
+
"encoder_repetition_penalty": req.encoder_repetition_penalty,
|
| 434 |
+
"max_time": req.max_time,
|
| 435 |
+
"diversity_penalty": req.diversity_penalty,
|
| 436 |
+
"num_beam_groups": req.num_beam_groups if not req.stream else 1,
|
| 437 |
+
"length_normalization_factor": req.length_normalization_factor,
|
| 438 |
+
"min_new_tokens": req.min_new_tokens,
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
direct_generate_params = {
|
| 442 |
+
"bad_words_ids": req.bad_words_ids,
|
| 443 |
+
"sequence_bias": req.sequence_bias,
|
| 444 |
+
"negative_prompt_ids": req.negative_prompt_ids,
|
| 445 |
+
"return_dict_in_generate": req.return_dict_in_generate,
|
| 446 |
+
"output_scores": req.output_scores and not req.stream,
|
| 447 |
+
"output_attentions": req.output_attentions and not req.stream,
|
| 448 |
+
"output_hidden_states": req.output_hidden_states and not req.stream,
|
| 449 |
+
"return_generation_inputs": req.return_generation_inputs,
|
| 450 |
+
"return_unused_generate_parameters": req.return_unused_generate_parameters,
|
| 451 |
+
"frequency_penalty": req.frequency_penalty,
|
| 452 |
+
"presence_penalty": req.presence_penalty,
|
| 453 |
+
"output_watermark": req.output_watermark,
|
| 454 |
+
"do_normalize_logits": req.do_normalize_logits,
|
| 455 |
+
"prompt_lookup_num_tokens": req.prompt_lookup_num_tokens,
|
| 456 |
+
"tfs_lambda": req.tfs_lambda,
|
| 457 |
+
"top_p_min": req.top_p_min,
|
| 458 |
+
"typical_p_min": req.typical_p_min,
|
| 459 |
+
}
|
| 460 |
+
if req.use_mirostat:
|
| 461 |
+
direct_generate_params['use_mirostat_mode'] = 1
|
| 462 |
+
direct_generate_params['mirostat_tau'] = req.mirostat_tau
|
| 463 |
+
direct_generate_params['mirostat_eta'] = req.mirostat_eta
|
| 464 |
+
else:
|
| 465 |
+
direct_generate_params['use_mirostat_mode'] = 0
|
| 466 |
+
|
| 467 |
+
if req.model_kwargs:
|
| 468 |
+
direct_generate_params['model_kwargs'] = req.model_kwargs
|
| 469 |
+
|
| 470 |
+
if req.generation_kwargs:
|
| 471 |
+
direct_generate_params.update(req.generation_kwargs)
|
| 472 |
+
|
| 473 |
+
gen_cfg_params_filtered = {k: v for k, v in gen_cfg_params.items() if v is not None}
|
| 474 |
+
direct_generate_params_filtered = {k: v for k, v in direct_generate_params.items() if v is not None}
|
| 475 |
+
|
| 476 |
+
try:
|
| 477 |
+
gen_cfg = GenerationConfig(**gen_cfg_params_filtered)
|
| 478 |
+
except Exception as e:
|
| 479 |
+
raise ValueError(f"Error creating GenerationConfig: {e}. Parameters: {gen_cfg_params_filtered}")
|
| 480 |
+
|
| 481 |
+
return gen_cfg, stopping_criteria, logits_processor, direct_generate_params_filtered
|
| 482 |
+
|
| 483 |
+
def post_process_text(text: str, strip_trailing_whitespace: bool, remove_incomplete_sentences: bool) -> str:
|
| 484 |
+
if strip_trailing_whitespace:
|
| 485 |
+
text = text.rstrip()
|
| 486 |
+
if remove_incomplete_sentences:
|
| 487 |
+
for terminator in ['.', '!', '?', '\n']:
|
| 488 |
+
last_terminator = text.rfind(terminator)
|
| 489 |
+
if last_terminator != -1:
|
| 490 |
+
text = text[:last_terminator + 1]
|
| 491 |
+
break
|
| 492 |
+
return text
|
| 493 |
+
|
| 494 |
+
async def stream_generation_logic(req: GenerateRequest, initial_ids: torch.Tensor, gen_cfg: GenerationConfig, stopping_criteria: StoppingCriteriaList, logits_processor: LogitsProcessorList, direct_generate_params: Dict[str, Any], device: str) -> AsyncGenerator[str, None]:
|
| 495 |
+
queue = SyncToAsyncQueue()
|
| 496 |
+
streamer = QueueTextStreamer(queue, global_tokenizer, skip_prompt=True)
|
| 497 |
+
initial_len = initial_ids.shape[-1]
|
| 498 |
+
|
| 499 |
+
generation_future = asyncio.get_event_loop().run_in_executor(
|
| 500 |
+
executor,
|
| 501 |
+
lambda: global_model.generate(
|
| 502 |
+
initial_ids,
|
| 503 |
+
generation_config=gen_cfg,
|
| 504 |
+
stopping_criteria=stopping_criteria,
|
| 505 |
+
logits_processor=logits_processor,
|
| 506 |
+
streamer=streamer,
|
| 507 |
+
return_dict_in_generate=True,
|
| 508 |
+
**direct_generate_params,
|
| 509 |
+
)
|
| 510 |
+
)
|
| 511 |
+
|
| 512 |
+
try:
|
| 513 |
+
accumulated_text = ""
|
| 514 |
+
while True:
|
| 515 |
+
try:
|
| 516 |
+
item = await queue.get()
|
| 517 |
+
if item["type"] == "stream_end_signal":
|
| 518 |
+
break
|
| 519 |
+
if item["type"] == "token":
|
| 520 |
+
yield json.dumps(item) + "\n"
|
| 521 |
+
accumulated_text += item.get("text", "")
|
| 522 |
+
elif item["type"] == "error":
|
| 523 |
+
yield json.dumps(item) + "\n"
|
| 524 |
+
except asyncio.QueueClosed:
|
| 525 |
+
break
|
| 526 |
+
|
| 527 |
+
output = await asyncio.wrap_future(generation_future)
|
| 528 |
+
|
| 529 |
+
except Exception as e:
|
| 530 |
+
yield json.dumps({"type": "error", "message": f"Stream processing or generation error: {e}"}) + "\n"
|
| 531 |
+
if generation_future and not generation_future.done():
|
| 532 |
+
generation_future.cancel()
|
| 533 |
+
output = None
|
| 534 |
+
finally:
|
| 535 |
+
queue.close()
|
| 536 |
+
|
| 537 |
+
final_ids_generated = []
|
| 538 |
+
full_sequence_ids = initial_ids.tolist()[0]
|
| 539 |
+
total_generated_tokens = 0
|
| 540 |
+
finish_reason = "error"
|
| 541 |
+
final_text_processed = ""
|
| 542 |
+
|
| 543 |
+
if output is not None and isinstance(output, GenerationOutput) and output.sequences is not None and output.sequences.shape[0] > 0:
|
| 544 |
+
final_sequence = output.sequences[0]
|
| 545 |
+
final_ids_generated = final_sequence[initial_len:].tolist()
|
| 546 |
+
total_generated_tokens = len(final_ids_generated)
|
| 547 |
+
full_sequence_ids = final_sequence.tolist()
|
| 548 |
+
|
| 549 |
+
final_text_raw = global_tokenizer.decode(final_ids_generated, skip_special_tokens=True)
|
| 550 |
+
if req.stop_sequences:
|
| 551 |
+
for stop_seq in req.stop_sequences:
|
| 552 |
+
if stop_seq and stop_seq in final_text_raw:
|
| 553 |
+
final_text_raw = final_text_raw.split(stop_seq, 1)[0]
|
| 554 |
+
break
|
| 555 |
+
final_text_processed = post_process_text(final_text_raw, req.strip_trailing_whitespace, req.remove_incomplete_sentences)
|
| 556 |
+
|
| 557 |
+
if hasattr(output, 'stopping_criteria') and output.stopping_criteria is not None:
|
| 558 |
+
stopped_by = output.stopping_criteria
|
| 559 |
+
if any(isinstance(c, MaxLengthCriteria) for c in stopped_by):
|
| 560 |
+
finish_reason = "max_length"
|
| 561 |
+
elif any(isinstance(c, StopSequenceCriteria) for c in stopped_by):
|
| 562 |
+
finish_reason = "stop_sequence"
|
| 563 |
+
else:
|
| 564 |
+
finish_reason = "stop"
|
| 565 |
+
if finish_reason == "error":
|
| 566 |
+
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id")
|
| 567 |
+
if eos_token_id is not None and final_ids_generated and final_ids_generated[-1] == eos_token_id:
|
| 568 |
+
finish_reason = "eos_token"
|
| 569 |
+
elif total_generated_tokens >= (req.max_new_tokens if req.max_new_tokens is not None else MAX_GENERATION_TOKENS):
|
| 570 |
+
finish_reason = "max_new_tokens"
|
| 571 |
+
elif req.max_length is not None and len(full_sequence_ids) >= req.max_length:
|
| 572 |
+
finish_reason = "max_length"
|
| 573 |
+
elif req.stop_sequences and any(seq in post_process_text(global_tokenizer.decode(final_ids_generated, skip_special_tokens=True), False, False) for seq in req.stop_sequences):
|
| 574 |
+
finish_reason = "stop_sequence"
|
| 575 |
+
elif hasattr(output, 'max_time_exceeded') and output.max_time_exceeded:
|
| 576 |
+
finish_reason = "time"
|
| 577 |
+
else:
|
| 578 |
+
finish_reason = "unknown"
|
| 579 |
+
|
| 580 |
+
else:
|
| 581 |
+
finish_reason = "error_or_no_output"
|
| 582 |
+
|
| 583 |
+
final_payload: Dict[str, Any] = {
|
| 584 |
+
"type": "done",
|
| 585 |
+
"total_prompt_tokens": initial_len,
|
| 586 |
+
"total_generated_tokens": total_generated_tokens,
|
| 587 |
+
"total_sequence_tokens": len(full_sequence_ids),
|
| 588 |
+
"final_text": final_text_processed,
|
| 589 |
+
"finish_reason": finish_reason
|
| 590 |
+
}
|
| 591 |
+
if not req.remove_input_from_output:
|
| 592 |
+
final_payload["full_sequence_token_ids"] = full_sequence_ids
|
| 593 |
+
final_payload["generated_token_ids"] = final_ids_generated
|
| 594 |
+
yield json.dumps(final_payload) + "\n"
|
| 595 |
+
|
| 596 |
+
await cleanup(device)
|
| 597 |
+
|
| 598 |
+
async def non_stream_generation_logic(req: GenerateRequest, initial_ids: torch.Tensor, gen_cfg: GenerationConfig, stopping_criteria: StoppingCriteriaList, logits_processor: LogitsProcessorList, direct_generate_params: Dict[str, Any], device: str) -> Dict[str, Any]:
|
| 599 |
+
try:
|
| 600 |
+
generate_params = gen_cfg.to_dict()
|
| 601 |
+
generate_params.update({
|
| 602 |
+
"input_ids": initial_ids,
|
| 603 |
+
"stopping_criteria": stopping_criteria if stopping_criteria else None,
|
| 604 |
+
"logits_processor": logits_processor if logits_processor else None,
|
| 605 |
+
**direct_generate_params,
|
| 606 |
+
})
|
| 607 |
+
|
| 608 |
+
if req.generation_kwargs:
|
| 609 |
+
generate_params.update(req.generation_kwargs)
|
| 610 |
+
|
| 611 |
+
with torch.no_grad():
|
| 612 |
+
out = await asyncio.to_thread(global_model.generate, **generate_params)
|
| 613 |
+
|
| 614 |
+
generated_data = []
|
| 615 |
+
initial_len = initial_ids.shape[-1]
|
| 616 |
+
eos_token_id = req.eos_token_id_override if req.eos_token_id_override is not None else global_tokens.get("eos_token_id")
|
| 617 |
+
effective_max_length = req.max_length if req.max_length is not None and req.max_length > 0 else initial_len + (req.max_new_tokens if req.max_new_tokens is not None else MAX_GENERATION_TOKENS)
|
| 618 |
+
|
| 619 |
+
for i in range(req.num_return_sequences):
|
| 620 |
+
if i >= out.sequences.shape[0]:
|
| 621 |
+
break
|
| 622 |
+
sequence = out.sequences[i]
|
| 623 |
+
generated_ids_tensor = sequence[initial_len:]
|
| 624 |
+
full_sequence_ids = sequence.tolist()
|
| 625 |
+
text_raw = global_tokenizer.decode(generated_ids_tensor, skip_special_tokens=True)
|
| 626 |
+
|
| 627 |
+
if req.stop_sequences:
|
| 628 |
+
for stop_seq in req.stop_sequences:
|
| 629 |
+
if stop_seq and stop_seq in text_raw:
|
| 630 |
+
text_raw = text_raw.split(stop_seq, 1)[0]
|
| 631 |
+
break
|
| 632 |
+
|
| 633 |
+
text_processed = post_process_text(text_raw, req.strip_trailing_whitespace, req.remove_incomplete_sentences)
|
| 634 |
+
generated_tokens_count = len(generated_ids_tensor)
|
| 635 |
+
|
| 636 |
+
finish_reason = "unknown"
|
| 637 |
+
if hasattr(out, 'stopping_criteria') and out.stopping_criteria is not None:
|
| 638 |
+
stopped_by_criteria = out.stopping_criteria
|
| 639 |
+
if any(isinstance(c, MaxLengthCriteria) for c in stopped_by_criteria):
|
| 640 |
+
finish_reason = "max_length"
|
| 641 |
+
elif any(isinstance(c, StopSequenceCriteria) for c in stopped_by_criteria):
|
| 642 |
+
finish_reason = "stop_sequence"
|
| 643 |
+
else:
|
| 644 |
+
finish_reason = "stop"
|
| 645 |
+
if finish_reason == "unknown":
|
| 646 |
+
if eos_token_id is not None and generated_ids_tensor.numel() > 0 and generated_ids_tensor[-1] == eos_token_id:
|
| 647 |
+
finish_reason = "eos_token"
|
| 648 |
+
elif len(full_sequence_ids) >= effective_max_length:
|
| 649 |
+
if req.max_new_tokens is not None and generated_tokens_count >= req.max_new_tokens:
|
| 650 |
+
finish_reason = "max_new_tokens"
|
| 651 |
+
elif req.max_length is not None and len(full_sequence_ids) >= req.max_length:
|
| 652 |
+
finish_reason = "max_length"
|
| 653 |
+
else:
|
| 654 |
+
finish_reason = "length"
|
| 655 |
+
elif req.stop_sequences and any(seq in text_processed for seq in req.stop_sequences):
|
| 656 |
+
finish_reason = "stop_sequence"
|
| 657 |
+
elif hasattr(out, 'max_time_exceeded') and out.max_time_exceeded:
|
| 658 |
+
finish_reason = "time"
|
| 659 |
+
else:
|
| 660 |
+
finish_reason = "stop"
|
| 661 |
+
|
| 662 |
+
item_data: Dict[str, Any] = {
|
| 663 |
+
"text": text_processed if req.return_text_from_sequence else None,
|
| 664 |
+
"token_ids": generated_ids_tensor.tolist(),
|
| 665 |
+
"generated_tokens_count": generated_tokens_count,
|
| 666 |
+
"finish_reason": finish_reason
|
| 667 |
+
}
|
| 668 |
+
if not req.remove_input_from_output:
|
| 669 |
+
item_data["full_sequence_token_ids"] = full_sequence_ids
|
| 670 |
+
|
| 671 |
+
if req.output_scores and hasattr(out, 'scores') and out.scores is not None:
|
| 672 |
+
item_data["scores"] = [score.tolist() for score in out.scores] if isinstance(out.scores, tuple) else out.scores.tolist()
|
| 673 |
+
if req.return_token_logprobs:
|
| 674 |
+
try:
|
| 675 |
+
log_probs_list = []
|
| 676 |
+
if hasattr(out, 'scores') and out.scores is not None:
|
| 677 |
+
raw_scores = out.scores
|
| 678 |
+
if isinstance(raw_scores, tuple):
|
| 679 |
+
if raw_scores and raw_scores[0].ndim == 2:
|
| 680 |
+
step_logits_list = [s[i] for s in raw_scores]
|
| 681 |
+
elif raw_scores:
|
| 682 |
+
step_logits_list = raw_scores
|
| 683 |
+
else:
|
| 684 |
+
step_logits_list = []
|
| 685 |
+
elif raw_scores.ndim == 3:
|
| 686 |
+
step_logits_list = [raw_scores[step_idx, i] for step_idx in range(raw_scores.shape[0])]
|
| 687 |
+
elif raw_scores.ndim == 2:
|
| 688 |
+
step_logits_list = [raw_scores[step_idx] for step_idx in range(raw_scores.shape[0])]
|
| 689 |
+
else:
|
| 690 |
+
step_logits_list = []
|
| 691 |
+
|
| 692 |
+
step_log_probs = [F.log_softmax(l, dim=-1) for l in step_logits_list if l is not None and l.numel() > 0]
|
| 693 |
+
for step_idx, token_id in enumerate(generated_ids_tensor.tolist()):
|
| 694 |
+
if step_idx < len(step_log_probs) and token_id < step_log_probs[step_idx].shape[-1]:
|
| 695 |
+
log_probs_list.append(float(step_log_probs[step_idx][token_id].item()))
|
| 696 |
+
else:
|
| 697 |
+
log_probs_list.append(None)
|
| 698 |
+
item_data["token_logprobs"] = log_probs_list
|
| 699 |
+
else:
|
| 700 |
+
item_data["token_logprobs"] = "Scores not available."
|
| 701 |
+
except Exception:
|
| 702 |
+
item_data["token_logprobs"] = "Error calculating logprobs."
|
| 703 |
+
|
| 704 |
+
if req.output_attentions and hasattr(out, 'attentions') and out.attentions is not None:
|
| 705 |
+
item_data["attentions"] = "Attentions data is large and complex."
|
| 706 |
+
if req.output_hidden_states and hasattr(out, 'hidden_states') and out.hidden_states is not None:
|
| 707 |
+
item_data["hidden_states"] = "Hidden states data is large and complex."
|
| 708 |
+
if req.output_watermark and hasattr(out, 'watermark') and out.watermark is not None:
|
| 709 |
+
item_data["watermark"] = out.watermark[i] if isinstance(out.watermark, list) and len(out.watermark) > i else out.watermark
|
| 710 |
+
|
| 711 |
+
generated_data.append(item_data)
|
| 712 |
+
|
| 713 |
+
response_payload: Dict[str, Any] = {
|
| 714 |
+
"prompt_tokens": initial_len,
|
| 715 |
+
"generated_sequences": generated_data,
|
| 716 |
+
}
|
| 717 |
+
if req.num_return_sequences == 1 and generated_data:
|
| 718 |
+
response_payload["total_tokens"] = response_payload["prompt_tokens"] + generated_data[0]["generated_tokens_count"]
|
| 719 |
+
|
| 720 |
+
if req.return_dict_in_generate and isinstance(out, GenerationOutput):
|
| 721 |
+
raw_out_dict = {}
|
| 722 |
+
for key in out.keys():
|
| 723 |
+
if key not in ['sequences', 'scores', 'attentions', 'hidden_states', 'past_key_values', 'watermark', 'sequences_scores', 'beam_indices', 'stopping_criteria']:
|
| 724 |
+
value = out[key]
|
| 725 |
+
if isinstance(value, torch.Tensor):
|
| 726 |
+
raw_out_dict[key] = value.tolist()
|
| 727 |
+
else:
|
| 728 |
+
raw_out_dict[key] = value
|
| 729 |
+
response_payload["raw_generate_output"] = raw_out_dict
|
| 730 |
+
|
| 731 |
+
await cleanup(device)
|
| 732 |
+
return response_payload
|
| 733 |
+
|
| 734 |
+
except Exception as e:
|
| 735 |
+
await cleanup(device)
|
| 736 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Generation error: {e}")
|
| 737 |
+
|
| 738 |
+
async def cleanup(device: str):
|
| 739 |
+
if device == "cuda":
|
| 740 |
+
torch.cuda.empty_cache()
|
| 741 |
+
gc.collect()
|
| 742 |
+
|
| 743 |
+
app = FastAPI(title="Chatbot Profesional API", version="1.0.0")
|
| 744 |
+
|
| 745 |
+
@app.on_event("startup")
|
| 746 |
+
async def startup_event():
|
| 747 |
+
asyncio.create_task(load_model())
|
| 748 |
+
asyncio.create_task(rate_limit_reset_task())
|
| 749 |
+
|
| 750 |
+
async def load_model():
|
| 751 |
+
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR
|
| 752 |
+
try:
|
| 753 |
+
TORCH_DTYPE = getattr(torch, TORCH_DTYPE_STR.lower(), torch.float16)
|
| 754 |
+
if TORCH_DTYPE_STR.lower() == "bfloat16" and (not torch.cuda.is_available() or (torch.cuda.is_available() and not torch.cuda.get_device_properties(0).has_bfloat16)):
|
| 755 |
+
TORCH_DTYPE = torch.float16
|
| 756 |
+
elif not isinstance(TORCH_DTYPE, torch.dtype):
|
| 757 |
+
TORCH_DTYPE = torch.float16
|
| 758 |
+
except AttributeError:
|
| 759 |
+
TORCH_DTYPE = torch.float16
|
| 760 |
+
try:
|
| 761 |
+
config = AutoConfig.from_pretrained(MODEL_NAME, trust_remote_code=TRUST_REMOTE_CODE)
|
| 762 |
+
tokenizer_kwargs = {"config": config, "trust_remote_code": TRUST_REMOTE_CODE}
|
| 763 |
+
global_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, **tokenizer_kwargs)
|
| 764 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 765 |
+
model_kwargs = {"config": config, "torch_dtype": TORCH_DTYPE, "trust_remote_code": TRUST_REMOTE_CODE}
|
| 766 |
+
if ENABLE_FLASH_ATTENTION_2 and device == "cuda":
|
| 767 |
+
try:
|
| 768 |
+
if hasattr(config, '_supports_flash_attn_2') and config._supports_flash_attn_2:
|
| 769 |
+
model_kwargs['use_flash_attention_2'] = True
|
| 770 |
+
elif hasattr(AutoModelForCausalLM, '_supports_flash_attn_2') and AutoModelForCausalLM._supports_flash_attn_2(config):
|
| 771 |
+
model_kwargs['use_flash_attention_2'] = True
|
| 772 |
+
except Exception:
|
| 773 |
+
pass
|
| 774 |
+
global_model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, **model_kwargs)
|
| 775 |
+
global_model.to(device)
|
| 776 |
+
global_model.eval()
|
| 777 |
+
global_tokens["eos_token_id"] = global_tokenizer.eos_token_id
|
| 778 |
+
global_tokens["pad_token_id"] = global_tokenizer.pad_token_id
|
| 779 |
+
if global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is not None:
|
| 780 |
+
global_tokens["pad_token_id"] = global_tokens["eos_token_id"]
|
| 781 |
+
if global_model.config.pad_token_id is None:
|
| 782 |
+
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 783 |
+
except Exception:
|
| 784 |
+
global_model = None
|
| 785 |
+
global_tokenizer = None
|
| 786 |
+
global_tokens = {}
|
| 787 |
+
|
| 788 |
+
html_code = """
|
| 789 |
+
<!DOCTYPE html>
|
| 790 |
+
<html lang="es">
|
| 791 |
+
<head>
|
| 792 |
+
<meta charset="UTF-8" />
|
| 793 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 794 |
+
<title>Chatbot Profesional</title>
|
| 795 |
+
<style>
|
| 796 |
+
body { font-family: Arial, sans-serif; margin: 20px; display: flex; flex-direction: column; height: calc(100vh - 40px);}
|
| 797 |
+
#chatbox { flex-grow: 1; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; display: flex; flex-direction: column;}
|
| 798 |
+
.message { margin-bottom: 10px; padding: 8px; border-radius: 5px; max-width: 80%; word-wrap: break-word;}
|
| 799 |
+
.user-message { background-color: #e1ffc7; align-self: flex-end; }
|
| 800 |
+
.bot-message { background-color: #f1f0f0; align-self: flex-start; }
|
| 801 |
+
.system-message { background-color: #e0f7fa; align-self: flex-start; font-size: 0.9em; color: #555; }
|
| 802 |
+
.error-message { background-color: #ffebee; align-self: flex-start; font-size: 0.9em; color: #d32f2f; }
|
| 803 |
+
.message strong { display: block; margin-bottom: 4px; }
|
| 804 |
+
#input-area { display: flex; }
|
| 805 |
+
#user-input { flex-grow: 1; padding: 8px; box-sizing: border-box; margin-right: 5px;}
|
| 806 |
+
#send-btn { padding: 8px 15px;}
|
| 807 |
+
</style>
|
| 808 |
+
</head>
|
| 809 |
+
<body>
|
| 810 |
+
<h1>Chatbot Profesional (POST API)</h1>
|
| 811 |
+
<div id="chatbox"></div>
|
| 812 |
+
<div id="input-area">
|
| 813 |
+
<input type="text" id="user-input" placeholder="Escribe tu mensaje aquí..." autocomplete="off"/>
|
| 814 |
+
<button id="send-btn">Enviar</button>
|
| 815 |
+
</div>
|
| 816 |
+
<script>
|
| 817 |
+
const chatbox = document.getElementById('chatbox');
|
| 818 |
+
const userInput = document.getElementById('user-input');
|
| 819 |
+
const sendBtn = document.getElementById('send-btn');
|
| 820 |
+
let conversationHistory = [];
|
| 821 |
+
const DEFAULT_SYSTEM_PROMPT = "Eres un asistente profesional y servicial.";
|
| 822 |
+
let currentSystemPrompt = DEFAULT_SYSTEM_PROMPT;
|
| 823 |
+
let currentBotMessageElement = null;
|
| 824 |
+
|
| 825 |
+
function appendMessage(sender, text, type = 'bot', isStreaming = false) {
|
| 826 |
+
let msgDiv;
|
| 827 |
+
let contentElement;
|
| 828 |
+
if (isStreaming && currentBotMessageElement && type === 'bot') {
|
| 829 |
+
contentElement = currentBotMessageElement.querySelector('.message-content');
|
| 830 |
+
if (contentElement) {
|
| 831 |
+
contentElement.textContent += text;
|
| 832 |
+
} else {
|
| 833 |
+
msgDiv = document.createElement('div');
|
| 834 |
+
msgDiv.classList.add('message', `${type}-message`);
|
| 835 |
+
msgDiv.innerHTML = `<strong>${sender}:</strong> <span class="message-content">${text}</span>`;
|
| 836 |
+
chatbox.appendChild(msgDiv);
|
| 837 |
+
currentBotMessageElement = msgDiv;
|
| 838 |
+
}
|
| 839 |
+
} else {
|
| 840 |
+
msgDiv = document.createElement('div');
|
| 841 |
+
msgDiv.classList.add('message', `${type}-message`);
|
| 842 |
+
msgDiv.innerHTML = `<strong>${sender}:</strong> <span class="message-content">${text}</span>`;
|
| 843 |
+
chatbox.appendChild(msgDiv);
|
| 844 |
+
if (sender === 'Bot' && isStreaming) {
|
| 845 |
+
currentBotMessageElement = msgDiv;
|
| 846 |
+
} else {
|
| 847 |
+
currentBotMessageElement = null;
|
| 848 |
+
}
|
| 849 |
+
}
|
| 850 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
function updateHistory(role, content) {
|
| 854 |
+
conversationHistory.push({ "role": role, "content": content });
|
| 855 |
+
const maxHistorySize = 20;
|
| 856 |
+
if (conversationHistory.length > maxHistorySize) {
|
| 857 |
+
const systemPrompt = conversationHistory.find(m => m.role === 'system');
|
| 858 |
+
let startIndex = Math.max(1, conversationHistory.length - maxHistorySize);
|
| 859 |
+
let newHistory = conversationHistory.slice(startIndex);
|
| 860 |
+
if (systemPrompt && newHistory[0]?.role !== 'system') {
|
| 861 |
+
newHistory.unshift(systemPrompt);
|
| 862 |
+
}
|
| 863 |
+
conversationHistory = newHistory;
|
| 864 |
+
}
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
async function sendMessage() {
|
| 868 |
+
const text = userInput.value.trim();
|
| 869 |
+
if (!text) {
|
| 870 |
+
return;
|
| 871 |
+
}
|
| 872 |
+
appendMessage('Usuario', text, 'user');
|
| 873 |
+
updateHistory("user", text);
|
| 874 |
+
userInput.value = '';
|
| 875 |
+
sendBtn.disabled = true;
|
| 876 |
+
currentBotMessageElement = null;
|
| 877 |
+
|
| 878 |
+
const isStreamingRequest = true;
|
| 879 |
+
const requestPayload = {
|
| 880 |
+
input_text: text,
|
| 881 |
+
history: conversationHistory,
|
| 882 |
+
system_prompt: currentSystemPrompt,
|
| 883 |
+
stream: isStreamingRequest,
|
| 884 |
+
temperature: 0.7,
|
| 885 |
+
top_k: 0,
|
| 886 |
+
top_p: 0.9,
|
| 887 |
+
max_new_tokens: 512,
|
| 888 |
+
};
|
| 889 |
+
|
| 890 |
+
try {
|
| 891 |
+
const response = await fetch('/generate', {
|
| 892 |
+
method: 'POST',
|
| 893 |
+
headers: {
|
| 894 |
+
'Content-Type': 'application/json',
|
| 895 |
+
'X-API-Key': 'YOUR_SECRET_API_KEY'
|
| 896 |
+
},
|
| 897 |
+
body: JSON.stringify(requestPayload),
|
| 898 |
+
});
|
| 899 |
+
|
| 900 |
+
if (!response.ok) {
|
| 901 |
+
const errorData = await response.json();
|
| 902 |
+
throw new Error(`API Error: ${response.status} ${response.statusText} - ${errorData.detail || errorData.error || 'Unknown error'}`);
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
if (isStreamingRequest) {
|
| 906 |
+
const reader = response.body.getReader();
|
| 907 |
+
const decoder = new TextDecoder();
|
| 908 |
+
let buffer = '';
|
| 909 |
+
let finalBotText = "";
|
| 910 |
+
appendMessage('Bot', '', 'bot', true);
|
| 911 |
+
|
| 912 |
+
while (true) {
|
| 913 |
+
const { value, done } = await reader.read();
|
| 914 |
+
if (done) break;
|
| 915 |
+
buffer += decoder.decode(value, { stream: true });
|
| 916 |
+
const lines = buffer.split('\n');
|
| 917 |
+
buffer = lines.pop();
|
| 918 |
+
for (const line of lines) {
|
| 919 |
+
if (line.trim() === '') continue;
|
| 920 |
+
try {
|
| 921 |
+
const data = JSON.parse(line);
|
| 922 |
+
if (data.type === 'token') {
|
| 923 |
+
appendMessage('Bot', data.text, 'bot', true);
|
| 924 |
+
finalBotText += data.text;
|
| 925 |
+
} else if (data.type === 'done') {
|
| 926 |
+
appendMessage('System', `Generated ${data.total_generated_tokens} tokens. Finish reason: ${data.finish_reason}`, 'system');
|
| 927 |
+
updateHistory("assistant", data.final_text !== undefined ? data.final_text : finalBotText);
|
| 928 |
+
} else if (data.type === 'error') {
|
| 929 |
+
appendMessage('Error', `Stream Error: ${data.message}`, 'error');
|
| 930 |
+
updateHistory("assistant", finalBotText || "Error during streaming.");
|
| 931 |
+
finalBotText = "";
|
| 932 |
+
reader.cancel();
|
| 933 |
+
return;
|
| 934 |
+
}
|
| 935 |
+
} catch (e) {
|
| 936 |
+
console.error('Failed to parse stream chunk:', e, line);
|
| 937 |
+
appendMessage('Error', 'Failed to process stream chunk.', 'error');
|
| 938 |
+
updateHistory("assistant", finalBotText || "Error processing stream.");
|
| 939 |
+
finalBotText = "";
|
| 940 |
+
reader.cancel();
|
| 941 |
+
return;
|
| 942 |
+
}
|
| 943 |
+
}
|
| 944 |
+
}
|
| 945 |
+
if (buffer.trim() !== '') {
|
| 946 |
+
try {
|
| 947 |
+
const data = JSON.parse(buffer);
|
| 948 |
+
if (data.type === 'token') {
|
| 949 |
+
appendMessage('Bot', data.text, 'bot', true);
|
| 950 |
+
finalBotText += data.text;
|
| 951 |
+
} else if (data.type === 'done') {
|
| 952 |
+
appendMessage('System', `Generated ${data.total_generated_tokens} tokens. Finish reason: ${data.finish_reason}`, 'system');
|
| 953 |
+
updateHistory("assistant", data.final_text !== undefined ? data.final_text : finalBotText);
|
| 954 |
+
} else if (data.type === 'error') {
|
| 955 |
+
appendMessage('Error', `Stream Error: ${data.message}`, 'error');
|
| 956 |
+
updateHistory("assistant", finalBotText || "Error during streaming.");
|
| 957 |
+
finalBotText = "";
|
| 958 |
+
}
|
| 959 |
+
} catch (e) {
|
| 960 |
+
console.error('Failed to parse remaining buffer:', e, buffer);
|
| 961 |
+
appendMessage('Error', 'Failed to process remaining stream data.', 'error');
|
| 962 |
+
updateHistory("assistant", finalBotText || "Error processing remaining stream data.");
|
| 963 |
+
finalBotText = "";
|
| 964 |
+
}
|
| 965 |
+
}
|
| 966 |
+
} else {
|
| 967 |
+
const data = await response.json();
|
| 968 |
+
if (data.text !== undefined && Object.keys(data).length === 1) {
|
| 969 |
+
appendMessage('Bot', data.text, 'bot');
|
| 970 |
+
updateHistory("assistant", data.text);
|
| 971 |
+
} else if (data.generated_sequences && data.generated_sequences.length > 0) {
|
| 972 |
+
const generatedText = data.generated_sequences[0].text;
|
| 973 |
+
appendMessage('Bot', generatedText, 'bot');
|
| 974 |
+
updateHistory("assistant", generatedText);
|
| 975 |
+
appendMessage('System', `Generated ${data.generated_sequences[0].generated_tokens_count} tokens. Finish reason: ${data.generated_sequences[0].finish_reason}`, 'system');
|
| 976 |
+
} else if (data.error) {
|
| 977 |
+
appendMessage('Error', `API Error: ${data.error}`, 'error');
|
| 978 |
+
} else {
|
| 979 |
+
appendMessage('Error', 'Received unexpected non-streaming response format.', 'error');
|
| 980 |
+
console.error("Unexpected non-streaming response:", data);
|
| 981 |
+
}
|
| 982 |
+
}
|
| 983 |
+
} catch (error) {
|
| 984 |
+
console.error('Send message error:', error);
|
| 985 |
+
appendMessage('Error', error.message || 'An unknown error occurred.', 'error');
|
| 986 |
+
updateHistory("assistant", "Error generating response.");
|
| 987 |
+
} finally {
|
| 988 |
+
sendBtn.disabled = false;
|
| 989 |
+
currentBotMessageElement = null;
|
| 990 |
+
}
|
| 991 |
+
}
|
| 992 |
+
sendBtn.onclick = sendMessage;
|
| 993 |
+
userInput.addEventListener('keypress', function(event) {
|
| 994 |
+
if (event.key === 'Enter' && !sendBtn.disabled) {
|
| 995 |
+
event.preventDefault();
|
| 996 |
+
sendMessage();
|
| 997 |
+
}
|
| 998 |
+
});
|
| 999 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 1000 |
+
appendMessage('System', 'Chatbot cargado. ¡Hola!', 'system');
|
| 1001 |
+
});
|
| 1002 |
+
</script>
|
| 1003 |
+
</body>
|
| 1004 |
+
</html>
|
| 1005 |
+
"""
|
| 1006 |
+
|
| 1007 |
+
@app.get("/", response_class=HTMLResponse, summary="Interactive HTML interface")
|
| 1008 |
+
async def root():
|
| 1009 |
+
return HTMLResponse(content=html_code)
|
| 1010 |
+
|
| 1011 |
+
@app.get("/health", summary="Check API health and model status")
|
| 1012 |
+
async def health():
|
| 1013 |
+
if global_model is None or global_tokenizer is None:
|
| 1014 |
+
status_detail = "loading model"
|
| 1015 |
+
if global_tokenizer is None and global_model is not None:
|
| 1016 |
+
status_detail = "tokenizer not loaded"
|
| 1017 |
+
elif global_model is None and global_tokenizer is not None:
|
| 1018 |
+
status_detail = "model not loaded"
|
| 1019 |
+
elif global_model is None and global_tokenizer is None:
|
| 1020 |
+
status_detail = "model and tokenizer not loaded"
|
| 1021 |
+
return JSONResponse(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, content={"status": status_detail, "model_loaded": global_model is not None, "tokenizer_loaded": global_tokenizer is not None})
|
| 1022 |
+
try:
|
| 1023 |
+
dummy_input = "Hello"
|
| 1024 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 1025 |
+
encoded_dummy = global_tokenizer(dummy_input, return_tensors="pt").to(device)
|
| 1026 |
+
with torch.no_grad():
|
| 1027 |
+
_ = global_model.generate(encoded_dummy.input_ids[:, -1:], max_new_tokens=1, num_beams=1, do_sample=False)
|
| 1028 |
+
return {"status": "ok", "model_loaded": True, "tokenizer_loaded": True, "generation_test": "successful"}
|
| 1029 |
+
except Exception as e:
|
| 1030 |
+
return JSONResponse(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, content={"status": "model_loaded_but_generation_failed", "model_loaded": True, "tokenizer_loaded": True, "generation_test_error": str(e)})
|
| 1031 |
+
|
| 1032 |
+
|
| 1033 |
+
@app.get("/config", summary="Get API configuration", dependencies=[Depends(authenticate_api_key)])
|
| 1034 |
+
async def get_config():
|
| 1035 |
+
torch_dtype_str_out = str(TORCH_DTYPE).split('.')[-1] if isinstance(TORCH_DTYPE, torch.dtype) else str(TORCH_DTYPE)
|
| 1036 |
+
return JSONResponse({
|
| 1037 |
+
"model_name": MODEL_NAME,
|
| 1038 |
+
"system_prompt_default": SYSTEM_PROMPT,
|
| 1039 |
+
"max_context_tokens": MAX_CONTEXT_TOKENS,
|
| 1040 |
+
"max_generation_tokens": MAX_GENERATION_TOKENS,
|
| 1041 |
+
"cuda_available": torch.cuda.is_available(),
|
| 1042 |
+
"model_loaded": global_model is not None,
|
| 1043 |
+
"tokenizer_loaded": global_tokenizer is not None,
|
| 1044 |
+
"max_concurrent_generations": MAX_CONCURRENT_GENERATIONS,
|
| 1045 |
+
"trust_remote_code": TRUST_REMOTE_CODE,
|
| 1046 |
+
"enable_flash_attention_2": ENABLE_FLASH_ATTENTION_2,
|
| 1047 |
+
"torch_dtype": torch_dtype_str_out,
|
| 1048 |
+
"eos_token_id": global_tokens.get("eos_token_id"),
|
| 1049 |
+
"pad_token_id": global_tokens.get("pad_token_id"),
|
| 1050 |
+
"bos_token_id": global_tokenizer.bos_token_id if global_tokenizer else None,
|
| 1051 |
+
"request_rate_limit": REQUEST_RATE_LIMIT,
|
| 1052 |
+
"rate_limit_window_seconds": RATE_LIMIT_WINDOW_SECONDS,
|
| 1053 |
+
})
|
| 1054 |
+
|
| 1055 |
+
@app.get("/model/info", summary="Get model information", dependencies=[Depends(authenticate_api_key)])
|
| 1056 |
+
async def get_model_info():
|
| 1057 |
+
if global_model is None or global_tokenizer is None:
|
| 1058 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Model is not loaded.")
|
| 1059 |
+
model_config = global_model.config.to_dict()
|
| 1060 |
+
tokenizer_config = global_tokenizer.init_kwargs
|
| 1061 |
+
special_tokens = {
|
| 1062 |
+
"eos_token": global_tokenizer.eos_token,
|
| 1063 |
+
"eos_token_id": global_tokenizer.eos_token_id,
|
| 1064 |
+
"pad_token": global_tokenizer.pad_token,
|
| 1065 |
+
"pad_token_id": global_tokenizer.pad_token_id,
|
| 1066 |
+
"bos_token": global_tokenizer.bos_token,
|
| 1067 |
+
"bos_token_id": global_tokenizer.bos_token_id,
|
| 1068 |
+
"unk_token": global_tokenizer.unk_token,
|
| 1069 |
+
"unk_token_id": global_tokenizer.unk_token_id,
|
| 1070 |
+
}
|
| 1071 |
+
return JSONResponse({
|
| 1072 |
+
"model_name": MODEL_NAME,
|
| 1073 |
+
"model_config": model_config,
|
| 1074 |
+
"tokenizer_config": tokenizer_config,
|
| 1075 |
+
"special_tokens": special_tokens,
|
| 1076 |
+
"vocab_size": global_tokenizer.vocab_size,
|
| 1077 |
+
"max_model_length": global_tokenizer.model_max_length,
|
| 1078 |
+
"is_loaded": True,
|
| 1079 |
+
})
|
| 1080 |
+
|
| 1081 |
+
@app.get("/tokenizer/chat_template", summary="Get the model's chat template", dependencies=[Depends(authenticate_api_key)])
|
| 1082 |
+
async def get_chat_template():
|
| 1083 |
+
if global_tokenizer is None:
|
| 1084 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1085 |
+
if not hasattr(global_tokenizer, 'chat_template') or not global_tokenizer.chat_template:
|
| 1086 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tokenizer does not have a chat template.")
|
| 1087 |
+
return JSONResponse({"chat_template": global_tokenizer.chat_template})
|
| 1088 |
+
|
| 1089 |
+
|
| 1090 |
+
@app.post("/tokenize", summary="Tokenize text", dependencies=[Depends(authenticate_api_key)])
|
| 1091 |
+
async def tokenize_endpoint(req: TokenizeRequest):
|
| 1092 |
+
if global_tokenizer is None:
|
| 1093 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1094 |
+
try:
|
| 1095 |
+
text_to_tokenize = req.text
|
| 1096 |
+
if req.chat_format and isinstance(req.text, str):
|
| 1097 |
+
text_to_tokenize = format_conversation(req.text, [], None, global_tokenizer)
|
| 1098 |
+
req.add_special_tokens = False
|
| 1099 |
+
|
| 1100 |
+
tokenizer_kwargs_final = req.tokenizer_kwargs or {}
|
| 1101 |
+
if req.use_fast_tokenizer is not None:
|
| 1102 |
+
tokenizer_kwargs_final['use_fast'] = req.use_fast_tokenizer
|
| 1103 |
+
|
| 1104 |
+
return_tensors = req.return_tensors if req.return_tensors is not None else None
|
| 1105 |
+
if return_tensors is None and (req.return_token_type_ids or req.return_attention_mask or req.return_offsets_mapping or req.return_length or req.return_word_ids):
|
| 1106 |
+
return_tensors = "pt"
|
| 1107 |
+
|
| 1108 |
+
encoded = global_tokenizer(
|
| 1109 |
+
text_to_tokenize,
|
| 1110 |
+
add_special_tokens=req.add_special_tokens,
|
| 1111 |
+
return_token_type_ids=req.return_token_type_ids,
|
| 1112 |
+
padding=req.padding,
|
| 1113 |
+
truncation=req.truncation,
|
| 1114 |
+
max_length=req.max_length,
|
| 1115 |
+
is_split_into_words=req.is_split_into_words,
|
| 1116 |
+
return_tensors=return_tensors,
|
| 1117 |
+
return_attention_mask=req.return_attention_mask,
|
| 1118 |
+
return_offsets_mapping=req.return_offsets_mapping,
|
| 1119 |
+
return_length=req.return_length,
|
| 1120 |
+
return_word_ids=req.return_word_ids,
|
| 1121 |
+
verbose=req.verbose,
|
| 1122 |
+
**tokenizer_kwargs_final
|
| 1123 |
+
)
|
| 1124 |
+
if return_tensors:
|
| 1125 |
+
return JSONResponse({k: v.tolist() if isinstance(v, torch.Tensor) else v for k, v in encoded.items()})
|
| 1126 |
+
else:
|
| 1127 |
+
if isinstance(req.text, str):
|
| 1128 |
+
response_data = {
|
| 1129 |
+
"tokens": encoded["input_ids"],
|
| 1130 |
+
"token_count": len(encoded["input_ids"]),
|
| 1131 |
+
"input_text": req.text
|
| 1132 |
+
}
|
| 1133 |
+
if req.chat_format:
|
| 1134 |
+
response_data["formatted_text"] = text_to_tokenize
|
| 1135 |
+
if "token_type_ids" in encoded: response_data["token_type_ids"] = encoded["token_type_ids"]
|
| 1136 |
+
if "attention_mask" in encoded: response_data["attention_mask"] = encoded["attention_mask"]
|
| 1137 |
+
if "offset_mapping" in encoded: response_data["offset_mapping"] = encoded["offset_mapping"]
|
| 1138 |
+
if "length" in encoded: response_data["length"] = encoded["length"]
|
| 1139 |
+
if "word_ids" in encoded: response_data["word_ids"] = encoded["word_ids"]
|
| 1140 |
+
return JSONResponse(response_data)
|
| 1141 |
+
elif isinstance(req.text, list):
|
| 1142 |
+
results = []
|
| 1143 |
+
for i, item_text in enumerate(req.text):
|
| 1144 |
+
item_result: Dict[str, Any] = {"text": item_text}
|
| 1145 |
+
if i < len(encoded.get("input_ids", [])):
|
| 1146 |
+
item_result["tokens"] = encoded["input_ids"][i]
|
| 1147 |
+
item_result["token_count"] = len(encoded["input_ids"][i])
|
| 1148 |
+
if "token_type_ids" in encoded and i < len(encoded["token_type_ids"]):
|
| 1149 |
+
item_result["token_type_ids"] = encoded["token_type_ids"][i]
|
| 1150 |
+
if "attention_mask" in encoded and i < len(encoded["attention_mask"]):
|
| 1151 |
+
item_result["attention_mask"] = encoded["attention_mask"][i]
|
| 1152 |
+
if "offset_mapping" in encoded and i < len(encoded["offset_mapping"]):
|
| 1153 |
+
item_result["offset_mapping"] = encoded["offset_mapping"][i]
|
| 1154 |
+
if "length" in encoded and i < len(encoded["length"]):
|
| 1155 |
+
item_result["length"] = encoded["length"][i]
|
| 1156 |
+
if "word_ids" in encoded and i < len(encoded["word_ids"]):
|
| 1157 |
+
item_result["word_ids"] = encoded["word_ids"][i]
|
| 1158 |
+
else:
|
| 1159 |
+
item_result["tokens"] = []
|
| 1160 |
+
item_result["token_count"] = 0
|
| 1161 |
+
results.append(item_result)
|
| 1162 |
+
return JSONResponse(results)
|
| 1163 |
+
except Exception as e:
|
| 1164 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Tokenization error: {e}")
|
| 1165 |
+
|
| 1166 |
+
@app.post("/decode", summary="Decode token IDs to text", dependencies=[Depends(authenticate_api_key)])
|
| 1167 |
+
async def decode_endpoint(req: DecodeRequest):
|
| 1168 |
+
if global_tokenizer is None:
|
| 1169 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1170 |
+
try:
|
| 1171 |
+
decode_kwargs = req.decode_kwargs or {}
|
| 1172 |
+
text = global_tokenizer.decode(
|
| 1173 |
+
req.token_ids,
|
| 1174 |
+
skip_special_tokens=req.skip_special_tokens,
|
| 1175 |
+
clean_up_tokenization_spaces=req.clean_up_tokenization_spaces,
|
| 1176 |
+
**decode_kwargs
|
| 1177 |
+
)
|
| 1178 |
+
return JSONResponse({"text": text})
|
| 1179 |
+
except Exception as e:
|
| 1180 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Decoding error: {e}")
|
| 1181 |
+
|
| 1182 |
+
@app.post("/system_prompt", summary="Update global system prompt", dependencies=[Depends(authenticate_api_key)])
|
| 1183 |
+
async def update_system_prompt(req: SystemPromptUpdateRequest):
|
| 1184 |
+
global SYSTEM_PROMPT
|
| 1185 |
+
if req.system_prompt is not None:
|
| 1186 |
+
SYSTEM_PROMPT = req.system_prompt.strip()
|
| 1187 |
+
return JSONResponse({"status": "success", "message": "Global system prompt updated"})
|
| 1188 |
+
else:
|
| 1189 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="System prompt cannot be null")
|
| 1190 |
+
|
| 1191 |
+
@app.get("/system_prompt", summary="Get global system prompt", dependencies=[Depends(authenticate_api_key)])
|
| 1192 |
+
async def get_system_prompt():
|
| 1193 |
+
return JSONResponse({"system_prompt": SYSTEM_PROMPT})
|
| 1194 |
+
|
| 1195 |
+
@app.post("/model/reload", summary="Reload the model", dependencies=[Depends(authenticate_api_key)])
|
| 1196 |
+
async def reload_model(req: ModelReloadRequest):
|
| 1197 |
+
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR
|
| 1198 |
+
new_model_name = req.model_name if req.model_name else MODEL_NAME
|
| 1199 |
+
new_trust_remote_code = req.trust_remote_code if req.trust_remote_code is not None else TRUST_REMOTE_CODE
|
| 1200 |
+
new_enable_flash_attention_2 = req.enable_flash_attention_2 if req.enable_flash_attention_2 is not None else ENABLE_FLASH_ATTENTION_2
|
| 1201 |
+
new_torch_dtype_str_req = req.torch_dtype if req.torch_dtype else TORCH_DTYPE_STR
|
| 1202 |
+
try:
|
| 1203 |
+
new_torch_dtype = getattr(torch, new_torch_dtype_str_req.lower())
|
| 1204 |
+
if new_torch_dtype_str_req.lower() == "bfloat16" and (not torch.cuda.is_available() or (torch.cuda.is_available() and not torch.cuda.get_device_properties(0).has_bfloat16)):
|
| 1205 |
+
new_torch_dtype = torch.float16
|
| 1206 |
+
elif not isinstance(new_torch_dtype, torch.dtype):
|
| 1207 |
+
raise AttributeError
|
| 1208 |
+
new_torch_dtype_str = str(new_torch_dtype).split('.')[-1]
|
| 1209 |
+
except AttributeError:
|
| 1210 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid or unsupported torch_dtype: {new_torch_dtype_str_req}")
|
| 1211 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 1212 |
+
async def _reload_model():
|
| 1213 |
+
global global_model, global_tokenizer, global_tokens, MODEL_NAME, TRUST_REMOTE_CODE, ENABLE_FLASH_ATTENTION_2, TORCH_DTYPE, TORCH_DTYPE_STR
|
| 1214 |
+
try:
|
| 1215 |
+
await cleanup(device)
|
| 1216 |
+
if global_model is not None:
|
| 1217 |
+
del global_model
|
| 1218 |
+
global_model = None
|
| 1219 |
+
if global_tokenizer is not None:
|
| 1220 |
+
del global_tokenizer
|
| 1221 |
+
global_tokenizer = None
|
| 1222 |
+
global_tokens = {}
|
| 1223 |
+
torch.cuda.empty_cache()
|
| 1224 |
+
gc.collect()
|
| 1225 |
+
config = AutoConfig.from_pretrained(new_model_name, trust_remote_code=new_trust_remote_code)
|
| 1226 |
+
tokenizer_kwargs = {"config": config, "trust_remote_code": new_trust_remote_code}
|
| 1227 |
+
if req.tokenizer_kwargs:
|
| 1228 |
+
tokenizer_kwargs.update(req.tokenizer_kwargs)
|
| 1229 |
+
tokenizer = AutoTokenizer.from_pretrained(new_model_name, **tokenizer_kwargs)
|
| 1230 |
+
model_kwargs = {"config": config, "torch_dtype": new_torch_dtype, "trust_remote_code": new_trust_remote_code}
|
| 1231 |
+
if new_enable_flash_attention_2 and device == "cuda":
|
| 1232 |
+
try:
|
| 1233 |
+
if hasattr(config, '_supports_flash_attn_2') and config._supports_flash_attn_2:
|
| 1234 |
+
model_kwargs['use_flash_attention_2'] = True
|
| 1235 |
+
elif hasattr(AutoModelForCausalLM, '_supports_flash_attn_2') and AutoModelForCausalLM._supports_flash_attn_2(config):
|
| 1236 |
+
model_kwargs['use_flash_attention_2'] = True
|
| 1237 |
+
except Exception:
|
| 1238 |
+
pass
|
| 1239 |
+
if req.model_kwargs:
|
| 1240 |
+
model_kwargs.update(req.model_kwargs)
|
| 1241 |
+
model = AutoModelForCausalLM.from_pretrained(new_model_name, **model_kwargs)
|
| 1242 |
+
model.to(device)
|
| 1243 |
+
model.eval()
|
| 1244 |
+
global_model = model
|
| 1245 |
+
global_tokenizer = tokenizer
|
| 1246 |
+
global_tokens["eos_token_id"] = global_tokenizer.eos_token_id
|
| 1247 |
+
global_tokens["pad_token_id"] = global_tokenizer.pad_token_id
|
| 1248 |
+
if global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is not None:
|
| 1249 |
+
global_tokens["pad_token_id"] = global_tokens["eos_token_id"]
|
| 1250 |
+
if global_model.config.pad_token_id is None:
|
| 1251 |
+
global_model.config.pad_token_id = global_tokens["pad_token_id"]
|
| 1252 |
+
elif global_tokens["pad_token_id"] is None and global_tokens["eos_token_id"] is None:
|
| 1253 |
+
pass
|
| 1254 |
+
MODEL_NAME = new_model_name
|
| 1255 |
+
TRUST_REMOTE_CODE = new_trust_remote_code
|
| 1256 |
+
ENABLE_FLASH_ATTENTION_2 = new_enable_flash_attention_2
|
| 1257 |
+
TORCH_DTYPE = new_torch_dtype
|
| 1258 |
+
TORCH_DTYPE_STR = new_torch_dtype_str
|
| 1259 |
+
except Exception as e:
|
| 1260 |
+
global_model = None
|
| 1261 |
+
global_tokenizer = None
|
| 1262 |
+
global_tokens = {}
|
| 1263 |
+
asyncio.create_task(_reload_model())
|
| 1264 |
+
return JSONResponse({"status": "info", "message": f"Attempting to load model {new_model_name} in background. Check logs for status."})
|
| 1265 |
+
|
| 1266 |
+
@app.post("/model/unload", summary="Unload the model", dependencies=[Depends(authenticate_api_key)])
|
| 1267 |
+
async def unload_model():
|
| 1268 |
+
global global_model, global_tokenizer, global_tokens
|
| 1269 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 1270 |
+
try:
|
| 1271 |
+
await cleanup(device)
|
| 1272 |
+
if global_model is not None:
|
| 1273 |
+
del global_model
|
| 1274 |
+
global_model = None
|
| 1275 |
+
if global_tokenizer is not None:
|
| 1276 |
+
del global_tokenizer
|
| 1277 |
+
global_tokenizer = None
|
| 1278 |
+
global_tokens = {}
|
| 1279 |
+
torch.cuda.empty_cache()
|
| 1280 |
+
gc.collect()
|
| 1281 |
+
return JSONResponse({"status": "success", "message": "Model unloaded successfully."})
|
| 1282 |
+
except Exception as e:
|
| 1283 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to unload model: {e}")
|
| 1284 |
+
|
| 1285 |
+
@app.get("/queue/status", summary="Get current generation queue status", dependencies=[Depends(authenticate_api_key)])
|
| 1286 |
+
async def queue_status():
|
| 1287 |
+
try:
|
| 1288 |
+
current_running = MAX_CONCURRENT_GENERATIONS - generation_semaphore._value
|
| 1289 |
+
return JSONResponse({
|
| 1290 |
+
"max_concurrent": MAX_CONCURRENT_GENERATIONS,
|
| 1291 |
+
"currently_running": current_running,
|
| 1292 |
+
"available_slots": generation_semaphore._value
|
| 1293 |
+
})
|
| 1294 |
+
except Exception as e:
|
| 1295 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting queue status: {e}")
|
| 1296 |
+
|
| 1297 |
+
@app.get("/tokens/special", summary="Get special token IDs", dependencies=[Depends(authenticate_api_key)])
|
| 1298 |
+
async def get_special_tokens():
|
| 1299 |
+
if global_tokenizer is None:
|
| 1300 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Tokenizer is not loaded.")
|
| 1301 |
+
try:
|
| 1302 |
+
return JSONResponse({
|
| 1303 |
+
"eos_token": global_tokenizer.eos_token,
|
| 1304 |
+
"eos_token_id": global_tokenizer.eos_token_id,
|
| 1305 |
+
"pad_token": global_tokenizer.pad_token,
|
| 1306 |
+
"pad_token_id": global_tokenizer.pad_token_id,
|
| 1307 |
+
"bos_token": global_tokenizer.bos_token,
|
| 1308 |
+
"bos_token_id": global_tokenizer.bos_token_id,
|
| 1309 |
+
"unk_token": global_tokenizer.unk_token,
|
| 1310 |
+
"unk_token_id": global_tokenizer.unk_token_id,
|
| 1311 |
+
})
|
| 1312 |
+
except Exception as e:
|
| 1313 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting special tokens: {e}")
|
| 1314 |
+
|
| 1315 |
+
@app.post("/generate", summary="Generate text", dependencies=[Depends(authenticate_api_key)])
|
| 1316 |
+
async def generate_endpoint(req: GenerateRequest, request: Request):
|
| 1317 |
+
client_host = request.client.host if request.client else "unknown"
|
| 1318 |
+
if client_host in request_counts and request_counts[client_host] >= REQUEST_RATE_LIMIT:
|
| 1319 |
+
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Rate limit exceeded")
|
| 1320 |
+
request_counts[client_host] = request_counts.get(client_host, 0) + 1
|
| 1321 |
+
|
| 1322 |
+
if global_model is None or global_tokenizer is None:
|
| 1323 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Model is not loaded. It may still be loading or failed to load.")
|
| 1324 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 1325 |
+
apply_seed(req.seed)
|
| 1326 |
+
try:
|
| 1327 |
+
initial_prompt_text = format_conversation(req.input_text, req.history, req.system_prompt, global_tokenizer)
|
| 1328 |
+
except Exception as e:
|
| 1329 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error formatting conversation: {e}")
|
| 1330 |
+
try:
|
| 1331 |
+
tokenizer_encoding_kwargs = req.tokenizer_kwargs or {}
|
| 1332 |
+
if req.use_fast_tokenizer is not None:
|
| 1333 |
+
tokenizer_encoding_kwargs['use_fast'] = req.use_fast_tokenizer
|
| 1334 |
+
encoded = global_tokenizer(initial_prompt_text, return_tensors="pt", add_special_tokens=False, **tokenizer_encoding_kwargs).to(device)
|
| 1335 |
+
initial_ids_before_trunc = encoded.input_ids
|
| 1336 |
+
initial_prompt_tokens_count_before_trunc = initial_ids_before_trunc.shape[-1]
|
| 1337 |
+
ids = truncate_encoded_ids(initial_ids_before_trunc, MAX_CONTEXT_TOKENS)
|
| 1338 |
+
except Exception as e:
|
| 1339 |
+
await cleanup(device)
|
| 1340 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Tokenizer encoding error: {e}")
|
| 1341 |
+
if req.tokenize_only:
|
| 1342 |
+
await cleanup(device)
|
| 1343 |
+
return JSONResponse({
|
| 1344 |
+
"prompt_tokens_count": initial_prompt_tokens_count_before_trunc,
|
| 1345 |
+
"max_context_tokens": MAX_CONTEXT_TOKENS,
|
| 1346 |
+
"truncated": initial_prompt_tokens_count_before_trunc > MAX_CONTEXT_TOKENS,
|
| 1347 |
+
"input_text_processed": initial_prompt_text,
|
| 1348 |
+
"input_ids_truncated": ids.tolist()[0]
|
| 1349 |
+
})
|
| 1350 |
+
try:
|
| 1351 |
+
gen_cfg, stopping_criteria, logits_processor, direct_generate_params = get_generation_params(req, ids, global_tokenizer, device)
|
| 1352 |
+
except ValueError as e:
|
| 1353 |
+
await cleanup(device)
|
| 1354 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error creating generation parameters: {e}")
|
| 1355 |
+
except Exception as e:
|
| 1356 |
+
await cleanup(device)
|
| 1357 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Unexpected error getting generation parameters: {e}")
|
| 1358 |
+
|
| 1359 |
+
|
| 1360 |
+
async with generation_semaphore:
|
| 1361 |
+
if req.stream:
|
| 1362 |
+
if req.return_only_text:
|
| 1363 |
+
pass
|
| 1364 |
+
try:
|
| 1365 |
+
return StreamingResponse(stream_generation_logic(req, ids, gen_cfg, stopping_criteria, logits_processor, direct_generate_params, device), media_type="application/json")
|
| 1366 |
+
except Exception as e:
|
| 1367 |
+
await cleanup(device)
|
| 1368 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error setting up streaming generation: {e}")
|
| 1369 |
+
else:
|
| 1370 |
+
try:
|
| 1371 |
+
response_data = await non_stream_generation_logic(req, ids, gen_cfg, stopping_criteria, logits_processor, direct_generate_params, device)
|
| 1372 |
+
if req.return_only_text and response_data.get("generated_sequences"):
|
| 1373 |
+
if response_data["generated_sequences"]:
|
| 1374 |
+
return JSONResponse({"text": response_data["generated_sequences"][0].get("text", "")})
|
| 1375 |
+
else:
|
| 1376 |
+
return JSONResponse({"text": ""})
|
| 1377 |
+
return JSONResponse(response_data)
|
| 1378 |
+
except HTTPException:
|
| 1379 |
+
raise
|
| 1380 |
+
except Exception as e:
|
| 1381 |
+
await cleanup(device)
|
| 1382 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Non-streaming generation error: {e}")
|
| 1383 |
+
|
| 1384 |
+
if __name__ == "__main__":
|
| 1385 |
+
uvicorn.run(
|
| 1386 |
+
app, host="0.0.0.0", port=7860,
|
| 1387 |
+
log_level="critical",
|
| 1388 |
+
access_log=False
|
| 1389 |
+
)
|