Spaces:
Sleeping
Sleeping
ming commited on
Commit ·
f293bb9
1
Parent(s): 3570bfd
Harden HF V2 streaming + full traceback logging
Browse files- Normalize tokenizer output and force batch size = 1 for streamer
- Ensure attention_mask exists for all tokenizer paths
- Validate pad_token_id / eos_token_id with proper fallbacks
- Don't move inputs to single device when using device_map='auto'
- Log full exception trace (not just str(e)) for better debugging
- Add daemon=True to generation thread for proper cleanup
Fixes instant failure (~20ms) and empty error string issues during /api/v2/summarize/stream
app/services/hf_streaming_summarizer.py
CHANGED
|
@@ -172,7 +172,7 @@ class HFStreamingSummarizer:
|
|
| 172 |
temperature = temperature or settings.hf_temperature
|
| 173 |
top_p = top_p or settings.hf_top_p
|
| 174 |
|
| 175 |
-
#
|
| 176 |
if "t5" in settings.hf_model_id.lower():
|
| 177 |
full_prompt = f"summarize: {text}"
|
| 178 |
inputs_raw = self.tokenizer(full_prompt, return_tensors="pt", max_length=512, truncation=True)
|
|
@@ -181,31 +181,58 @@ class HFStreamingSummarizer:
|
|
| 181 |
else:
|
| 182 |
messages = [
|
| 183 |
{"role": "system", "content": prompt},
|
| 184 |
-
{"role": "user", "content": text}
|
| 185 |
]
|
|
|
|
| 186 |
if hasattr(self.tokenizer, "apply_chat_template") and self.tokenizer.chat_template:
|
| 187 |
inputs_raw = self.tokenizer.apply_chat_template(
|
| 188 |
-
messages,
|
|
|
|
|
|
|
|
|
|
| 189 |
)
|
| 190 |
else:
|
| 191 |
full_prompt = f"{prompt}\n\n{text}"
|
| 192 |
inputs_raw = self.tokenizer(full_prompt, return_tensors="pt")
|
| 193 |
|
| 194 |
-
# Normalize to dict regardless of
|
| 195 |
if isinstance(inputs_raw, dict):
|
| 196 |
inputs = inputs_raw
|
| 197 |
else:
|
| 198 |
inputs = {"input_ids": inputs_raw}
|
| 199 |
|
| 200 |
-
#
|
| 201 |
-
|
|
|
|
|
|
|
| 202 |
|
| 203 |
-
# Enforce batch size == 1 for streamer
|
| 204 |
for k, v in list(inputs.items()):
|
| 205 |
-
if v
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
# Create streamer for token-by-token output
|
| 211 |
streamer = TextIteratorStreamer(
|
|
@@ -214,7 +241,6 @@ class HFStreamingSummarizer:
|
|
| 214 |
skip_special_tokens=True
|
| 215 |
)
|
| 216 |
|
| 217 |
-
# Generation parameters - T5 models use different parameters
|
| 218 |
gen_kwargs = {
|
| 219 |
**inputs,
|
| 220 |
"streamer": streamer,
|
|
@@ -222,16 +248,13 @@ class HFStreamingSummarizer:
|
|
| 222 |
"do_sample": True,
|
| 223 |
"temperature": temperature,
|
| 224 |
"top_p": top_p,
|
| 225 |
-
"pad_token_id":
|
|
|
|
| 226 |
}
|
| 227 |
-
# Streamer
|
| 228 |
gen_kwargs["num_return_sequences"] = 1
|
| 229 |
|
| 230 |
-
|
| 231 |
-
generation_thread = threading.Thread(
|
| 232 |
-
target=self.model.generate,
|
| 233 |
-
kwargs=gen_kwargs
|
| 234 |
-
)
|
| 235 |
generation_thread.start()
|
| 236 |
|
| 237 |
# Stream tokens as they arrive
|
|
@@ -262,13 +285,14 @@ class HFStreamingSummarizer:
|
|
| 262 |
|
| 263 |
logger.info(f"✅ HuggingFace summarization completed in {latency_ms:.2f}ms")
|
| 264 |
|
| 265 |
-
except Exception
|
| 266 |
-
|
|
|
|
| 267 |
# Yield error chunk
|
| 268 |
yield {
|
| 269 |
"content": "",
|
| 270 |
"done": True,
|
| 271 |
-
"error":
|
| 272 |
}
|
| 273 |
|
| 274 |
async def check_health(self) -> bool:
|
|
|
|
| 172 |
temperature = temperature or settings.hf_temperature
|
| 173 |
top_p = top_p or settings.hf_top_p
|
| 174 |
|
| 175 |
+
# Build tokenized inputs (normalize return types across tokenizers)
|
| 176 |
if "t5" in settings.hf_model_id.lower():
|
| 177 |
full_prompt = f"summarize: {text}"
|
| 178 |
inputs_raw = self.tokenizer(full_prompt, return_tensors="pt", max_length=512, truncation=True)
|
|
|
|
| 181 |
else:
|
| 182 |
messages = [
|
| 183 |
{"role": "system", "content": prompt},
|
| 184 |
+
{"role": "user", "content": text}
|
| 185 |
]
|
| 186 |
+
|
| 187 |
if hasattr(self.tokenizer, "apply_chat_template") and self.tokenizer.chat_template:
|
| 188 |
inputs_raw = self.tokenizer.apply_chat_template(
|
| 189 |
+
messages,
|
| 190 |
+
tokenize=True,
|
| 191 |
+
add_generation_prompt=True,
|
| 192 |
+
return_tensors="pt"
|
| 193 |
)
|
| 194 |
else:
|
| 195 |
full_prompt = f"{prompt}\n\n{text}"
|
| 196 |
inputs_raw = self.tokenizer(full_prompt, return_tensors="pt")
|
| 197 |
|
| 198 |
+
# Normalize to dict regardless of return type
|
| 199 |
if isinstance(inputs_raw, dict):
|
| 200 |
inputs = inputs_raw
|
| 201 |
else:
|
| 202 |
inputs = {"input_ids": inputs_raw}
|
| 203 |
|
| 204 |
+
# Ensure attention_mask (some tokenizers/models don't return it by default)
|
| 205 |
+
if "attention_mask" not in inputs and "input_ids" in inputs:
|
| 206 |
+
import torch
|
| 207 |
+
inputs["attention_mask"] = torch.ones_like(inputs["input_ids"])
|
| 208 |
|
| 209 |
+
# Enforce batch size == 1 for streamer
|
| 210 |
for k, v in list(inputs.items()):
|
| 211 |
+
if hasattr(v, "dim"):
|
| 212 |
+
if v.dim() == 1:
|
| 213 |
+
inputs[k] = v.unsqueeze(0) # [seq] -> [1, seq]
|
| 214 |
+
elif v.dim() >= 2 and v.size(0) > 1:
|
| 215 |
+
inputs[k] = v[:1] # [B, ...] -> [1, ...]
|
| 216 |
+
|
| 217 |
+
# IMPORTANT: with device_map="auto", let HF move tensors as needed.
|
| 218 |
+
# If you are *not* using device_map="auto", uncomment the line below:
|
| 219 |
+
# inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
|
| 220 |
+
|
| 221 |
+
# Validate pad/eos ids
|
| 222 |
+
pad_id = self.tokenizer.pad_token_id
|
| 223 |
+
eos_id = self.tokenizer.eos_token_id
|
| 224 |
+
if pad_id is None and eos_id is not None:
|
| 225 |
+
pad_id = eos_id
|
| 226 |
+
elif pad_id is None and eos_id is None:
|
| 227 |
+
# Last resort: set pad to 0 to avoid None in generate()
|
| 228 |
+
pad_id = 0
|
| 229 |
+
|
| 230 |
+
# Helpful debug: log shapes once
|
| 231 |
+
try:
|
| 232 |
+
_shapes = {k: tuple(v.shape) for k, v in inputs.items() if hasattr(v, "shape")}
|
| 233 |
+
logger.debug(f"HF V2 inputs shapes: {_shapes}, pad_id={pad_id}, eos_id={eos_id}")
|
| 234 |
+
except Exception:
|
| 235 |
+
pass
|
| 236 |
|
| 237 |
# Create streamer for token-by-token output
|
| 238 |
streamer = TextIteratorStreamer(
|
|
|
|
| 241 |
skip_special_tokens=True
|
| 242 |
)
|
| 243 |
|
|
|
|
| 244 |
gen_kwargs = {
|
| 245 |
**inputs,
|
| 246 |
"streamer": streamer,
|
|
|
|
| 248 |
"do_sample": True,
|
| 249 |
"temperature": temperature,
|
| 250 |
"top_p": top_p,
|
| 251 |
+
"pad_token_id": pad_id,
|
| 252 |
+
"eos_token_id": eos_id,
|
| 253 |
}
|
| 254 |
+
# Streamer requires single sequence
|
| 255 |
gen_kwargs["num_return_sequences"] = 1
|
| 256 |
|
| 257 |
+
generation_thread = threading.Thread(target=self.model.generate, kwargs=gen_kwargs, daemon=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
generation_thread.start()
|
| 259 |
|
| 260 |
# Stream tokens as they arrive
|
|
|
|
| 285 |
|
| 286 |
logger.info(f"✅ HuggingFace summarization completed in {latency_ms:.2f}ms")
|
| 287 |
|
| 288 |
+
except Exception:
|
| 289 |
+
# Capture full traceback to aid debugging (the message may be empty otherwise)
|
| 290 |
+
logger.exception("❌ HuggingFace summarization failed with an exception")
|
| 291 |
# Yield error chunk
|
| 292 |
yield {
|
| 293 |
"content": "",
|
| 294 |
"done": True,
|
| 295 |
+
"error": "HF summarization failed. See server logs for traceback.",
|
| 296 |
}
|
| 297 |
|
| 298 |
async def check_health(self) -> bool:
|