batch_compare_npu.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. #!/usr/bin/env python3
  2. """
  3. Batch comparison: RAG vs Wiki using vLLM on NPU (fast!)
  4. Uses OpenAI-compatible API served by vLLM on Ascend NPU
  5. """
  6. import json
  7. import sys
  8. import time
  9. import urllib.request
  10. from datetime import datetime
  11. from pathlib import Path
  12. sys.path.insert(0, '/home/67/knowledge/maritime')
  13. from search_engine import search
  14. QUESTIONS_FILE = Path('/home/67/knowledge/maritime/test_200_questions.json')
  15. RESULTS_FILE = Path('/home/67/knowledge/maritime/batch_results_npu.json')
  16. WIKI_DIR = '/home/67/lambdagentpaas/agentexample/qaagent67wiki/wiki'
  17. # vLLM API on NPU
  18. VLLM_URL = "http://127.0.0.1:8000/v1/chat/completions"
  19. def llm_chat(messages, max_tokens=4096, temperature=0.3):
  20. """Call vLLM OpenAI-compatible API"""
  21. body = json.dumps({
  22. "model": "qwen2.5-32b",
  23. "messages": messages,
  24. "temperature": temperature,
  25. "max_tokens": max_tokens,
  26. }, ensure_ascii=False).encode('utf-8')
  27. req = urllib.request.Request(
  28. VLLM_URL,
  29. data=body,
  30. headers={"Content-Type": "application/json"},
  31. method="POST"
  32. )
  33. with urllib.request.urlopen(req, timeout=300) as resp:
  34. data = json.loads(resp.read())
  35. return data["choices"][0]["message"]["content"].strip()
  36. def rag_answer(question, top_k=5):
  37. t0 = time.time()
  38. results = search(question, top_k=top_k)
  39. context = '\n\n'.join([f'[doc{i+1}: {r["source"]}]\n{r["text"]}' for i, r in enumerate(results)])
  40. prompt = f"""你是一个海事领域问答助手。请严格基于以下参考文档回答问题。
  41. 如果文档中没有相关信息,请说明"文档中未找到相关信息"。
  42. 回答要具体,引用文档中的原文,标注来源 [来源: 文件名]。
  43. ## 参考文档
  44. {context}
  45. ## 问题
  46. {question}
  47. ## 回答"""
  48. answer = llm_chat([{'role': 'user', 'content': prompt}])
  49. elapsed = time.time() - t0
  50. return {
  51. 'answer': answer,
  52. 'sources': [r['source'] for r in results],
  53. 'elapsed': round(elapsed, 1),
  54. 'chunks_used': len(results),
  55. }
  56. def wiki_answer(question, top_k=5):
  57. t0 = time.time()
  58. wiki_path = Path(WIKI_DIR)
  59. wiki_pages = []
  60. for subdir in ['sources', 'entities', 'topics', 'analyses']:
  61. d = wiki_path / subdir
  62. if d.exists():
  63. for f in d.glob('*.md'):
  64. try:
  65. content = f.read_text(encoding='utf-8')
  66. q_chars = set(question)
  67. match_score = sum(1 for c in q_chars if c in content)
  68. if match_score > len(question) * 0.3:
  69. wiki_pages.append({'name': f.stem, 'content': content[:1500], 'score': match_score})
  70. except:
  71. pass
  72. wiki_pages.sort(key=lambda x: x['score'], reverse=True)
  73. wiki_pages = wiki_pages[:3]
  74. if wiki_pages:
  75. wiki_context = '\n\n'.join([f'[Wiki: {p["name"]}]\n{p["content"]}' for p in wiki_pages])
  76. source_type = 'wiki'
  77. else:
  78. results = search(question, top_k=top_k)
  79. compile_context = '\n\n'.join([f'[doc: {r["source"]}]\n{r["text"]}' for r in results])
  80. compile_prompt = f"""请阅读以下文档片段,提炼出与问题相关的核心知识点。
  81. 用结构化的方式组织,标注来源。
  82. 文档:
  83. {compile_context}
  84. 问题: {question}
  85. 请输出结构化的知识摘要:"""
  86. compiled = llm_chat([{'role': 'user', 'content': compile_prompt}])
  87. wiki_context = compiled
  88. source_type = 'compiled'
  89. safe_name = question[:30].replace('/', '_').replace(' ', '_')
  90. analysis_path = wiki_path / 'analyses' / f'{safe_name}.md'
  91. analysis_path.parent.mkdir(parents=True, exist_ok=True)
  92. with open(analysis_path, 'w', encoding='utf-8') as f:
  93. f.write(f'# {question}\n\n{compiled}\n')
  94. answer_prompt = f"""你是一个海事领域 wiki 知识库问答助手。
  95. 基于以下已编译的 wiki 知识回答问题。回答要具体,标注来源。
  96. ## Wiki 知识
  97. {wiki_context}
  98. ## 问题
  99. {question}
  100. ## 回答"""
  101. answer = llm_chat([{'role': 'user', 'content': answer_prompt}])
  102. elapsed = time.time() - t0
  103. return {
  104. 'answer': answer,
  105. 'source_type': source_type,
  106. 'wiki_pages_used': len(wiki_pages),
  107. 'elapsed': round(elapsed, 1),
  108. }
  109. def save_results(results, meta):
  110. with open(RESULTS_FILE, 'w', encoding='utf-8') as f:
  111. json.dump({'meta': meta, 'results': results}, f, ensure_ascii=False, indent=2)
  112. def main():
  113. # Test vLLM connection
  114. print("Testing vLLM NPU connection...")
  115. try:
  116. test = llm_chat([{'role': 'user', 'content': 'Hello, respond with OK'}], max_tokens=10)
  117. print(f"vLLM OK: {test}")
  118. except Exception as e:
  119. print(f"vLLM connection failed: {e}")
  120. print("Make sure vLLM is running: bash /home/67/start_vllm_npu.sh")
  121. sys.exit(1)
  122. with open(QUESTIONS_FILE) as f:
  123. data = json.load(f)
  124. questions = data['questions']
  125. # Resume support
  126. existing = []
  127. done_ids = set()
  128. if RESULTS_FILE.exists():
  129. with open(RESULTS_FILE) as f:
  130. prev = json.load(f)
  131. existing = prev.get('results', [])
  132. done_ids = {r['question_id'] for r in existing}
  133. print(f"Resuming: {len(done_ids)} already done")
  134. results = list(existing)
  135. total = len(questions)
  136. remaining = [q for q in questions if q['id'] not in done_ids]
  137. print(f"Total: {total}, Remaining: {len(remaining)}")
  138. print(f"NPU inference: estimated ~3-5s per answer, total ~{len(remaining) * 8 / 60:.0f} min")
  139. print("=" * 60)
  140. start_time = time.time()
  141. for idx, q in enumerate(remaining):
  142. qnum = len(done_ids) + idx + 1
  143. print(f"\n[{qnum}/{total}] [{q['type']}/{q.get('difficulty','?')}] {q['question'][:60]}...")
  144. record = {
  145. 'question_id': q['id'],
  146. 'question': q['question'],
  147. 'type': q['type'],
  148. 'difficulty': q.get('difficulty', 'medium'),
  149. 'category': q.get('category', ''),
  150. }
  151. # RAG
  152. try:
  153. print(f" RAG...", end=' ', flush=True)
  154. rag = rag_answer(q['question'])
  155. record['rag'] = rag
  156. print(f"{rag['elapsed']}s, {len(rag['answer'])} chars")
  157. except Exception as e:
  158. record['rag'] = {'answer': f'ERROR: {e}', 'elapsed': 0, 'error': True}
  159. print(f"ERROR: {e}")
  160. # Wiki
  161. try:
  162. print(f" Wiki...", end=' ', flush=True)
  163. wiki = wiki_answer(q['question'])
  164. record['wiki'] = wiki
  165. print(f"{wiki['elapsed']}s, {len(wiki['answer'])} chars, src={wiki.get('source_type','?')}")
  166. except Exception as e:
  167. record['wiki'] = {'answer': f'ERROR: {e}', 'elapsed': 0, 'error': True}
  168. print(f"ERROR: {e}")
  169. results.append(record)
  170. # Save every 10 questions
  171. if (idx + 1) % 10 == 0 or idx == len(remaining) - 1:
  172. elapsed_total = time.time() - start_time
  173. avg_per_q = elapsed_total / (idx + 1)
  174. eta = avg_per_q * (len(remaining) - idx - 1)
  175. meta = {
  176. 'engine': 'vLLM on Ascend 910B4 NPU',
  177. 'model': 'Qwen2.5-32B-Instruct',
  178. 'completed': len(results),
  179. 'total': total,
  180. 'elapsed_minutes': round(elapsed_total / 60, 1),
  181. 'eta_minutes': round(eta / 60, 1),
  182. }
  183. save_results(results, meta)
  184. print(f" [Saved] {len(results)}/{total}, ETA: {meta['eta_minutes']}min")
  185. total_time = time.time() - start_time
  186. meta = {
  187. 'engine': 'vLLM on Ascend 910B4 NPU',
  188. 'model': 'Qwen2.5-32B-Instruct',
  189. 'completed_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  190. 'completed': len(results),
  191. 'total': total,
  192. 'total_minutes': round(total_time / 60, 1),
  193. }
  194. save_results(results, meta)
  195. print(f"\n{'=' * 60}")
  196. print(f"DONE! {len(results)} questions in {meta['total_minutes']} min")
  197. print(f"Results: {RESULTS_FILE}")
  198. if __name__ == '__main__':
  199. main()