news 2026/10/12 11:44:20

DeepSeek工程化落地手册:参数调优、上下文管理与部署避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepSeek工程化落地手册:参数调优、上下文管理与部署避坑

简介:一份聚焦大模型应用与私有化部署的实战手册,适合开发者、人工智能学习者及需要接入深度求索(DeepSeek)模型的团队。内容从基础操作讲起,覆盖推理与生成模型的多模型协同使用,以及文本、表格、图片等多种格式文件上传,并结合联网搜索与常用指令集,给出带背景、需求与约束的万能提问模板,便于快速上手。手册随后详解通过接口服务、本地工具或远程环境导入私有数据集的具体做法,同时解释关键参数含义、模型配置项和知识蒸馏原理,帮助读者理解模型机制并完成基础调优,这部分内容尤其适合希望进一步做模型轻量化或本地部署的读者。整份资料以单个docx文档收录,压缩包约207KB,轻量便携,在电脑或移动设备上均可随时查阅。已有285人学习下载,适合需要系统掌握深度求索模型使用方法与私有化部署要点的入门及进阶读者。

1. 从聊天到落地:这份手册真正解决的是什么

很多开发者第一次接触DeepSeek,是把它当成一个对话窗口来用的——问个问题、写段代码、润色文案,觉得“挺聪明”。但等到真把它接进业务系统,才发现事情完全不是那么回事:同样的提示词,在网页端表现不错,在API里却经常答非所问;多轮对话聊到第五轮就开始跑偏;并发一上来响应延迟直接翻倍。这份手册不讲聊天技巧,讲的是把DeepSeek作为一项工程能力落地时要面对的配置、逻辑和坑。适合两类人:一类是刚拿到API Key、打算做内部工具或原型产品的开发者,另一类是已经跑通基础调用、但总觉得结果不稳定、想搞清楚参数和基础设施在背后怎么起作用的人。读完你能知道模型参数往哪个方向调、上下文怎么管、部署起来要盯住什么指标,以及哪些问题其实是模型背不了锅的工程债。

2. 先搞懂DeepSeek的“性格”:推理逻辑与关键配置基础

2.1 模型家族怎么选:从对话、推理到代码生成的差异

DeepSeek不是一个单一模型,而是一组能力侧重点不同的模型版本。做应用选型时,第一步不是写代码,而是确定用哪个模型。常见的选择维度有三个:参数量、推理深度、上下文长度。参数量决定了模型的基础能力上限,推理深度影响复杂问题的拆解质量,上下文长度则直接决定了你的应用能承载多少对话历史或文档信息。

在实际项目中,我一般会把模型选择分成两档:简单任务——如文本分类、关键词抽取、格式转换,选通用对话模型,胜在速度快、成本低;复杂任务——如代码生成、多步推理、长文档理解,选带推理增强的模型,它会在内部生成更长的思考链,结果更稳,但响应时间会明显增加。

很多人在项目初期只用默认模型,等到发现问题才想起来换,这是反的。正确做法是先评估任务的复杂度和对响应速度的容忍度,再决定模型档位。一个经验值:如果单个任务在人工完成时不需要超过一分钟思考,通用对话模型就够了;如果任务要分步骤推导,就上推理增强型。

选型还涉及一个容易被忽略的问题:模型版本会迭代,接口上同一型号名的行为可能变化。因此,生产环境里不要把版本钉死在“最新”,要明确锁定具体版本号,否则一次上游升级就可能让你的输出格式、稳定性全部漂移。锁版本是落地的第一道保险。

2.2 温度、Top_p、最大长度:这组参数为什么是效果的地基

DeepSeek这类大模型的推理行为,本质上是在概率分布上采样。影响采样行为最直接的三个参数是temperature、top_p和max_tokens。这三者决定了同一个提示词下模型是“稳定输出”还是“发散想象”,是“给全答案”还是“给到一半断了”。

temperature控制的是概率分布的锐度:取值越低,模型越倾向选择高概率词,输出更确定;取值越高,低概率词被选中的可能性上升,输出更多样。一般做业务系统时,结构化输出、分类结果、代码生成,建议temperature设在0.1到0.3;头脑风暴、文案润色、创意生成,可以上到0.7到1.0。

top_p是核采样参数,控制候选词累积概率的截断。只调temperature经常不够,因为两者作用叠加。实务上把top_p固定在一个范围会省很多调试时间。比如对需要稳定格式的生成任务,top_p设置在0.5到0.9之间;对创意任务可以放宽到0.9以上。注意temperature和top_p不要同时调得很极端——你会发现输出会进入一种“既不稳定也不多样”的尴尬状态。

max_tokens则经常被当成一个防盗参数来设。很多人担心输出超长,就把max_tokens设成几百,结果是答案在关键位置被硬截断,留下一段语义残缺的JSON或代码。这不是模型的问题,是预算设错了。正确做法是估算任务的平均完成长度,留出1.5倍余量;超长任务应该走分段生成,而不是无脑拉大max_tokens。

2.3 一次接口调用看全生命周期:请求、状态码与信息量

把视角从模型参数拉到工程调用上,一次完整的DeepSeek API请求会经历:请求构造、网络传输、服务端排队、模型推理、响应返回五个环节。绝大多数“感觉变慢了”的抱怨,其实发生在队列和网络段,而不是模型本身。

请求构造阶段,你需要准备好model、messages、temperature、max_tokens这些字段;传输阶段受网络环境影响最大,跨地域调用时延迟会显著增加。服务端排队则取决于目标服务的负载和你账号的并发配额——这个过程对你的代码是黑匣子,但可以通过一个简单的办法感知:对比非高峰时段和高峰时段的同请求延迟,差异越大,说明排队影响越明显。

状态码是诊断问题的第一手材料。常见的三个:200表示成功,返回正常响应体;401代表鉴权失败,多数是API Key泄漏或过期;429表示触发限流,常见原因是并发超过配额,这时响应头里通常带Retry-After字段。看到429不要盲目重试,先看配额再决定退避策略。

响应体里除了content字段,还有一个容易被忽略的信息量来源:usage字段。它包含prompt_tokens和completion_tokens,这两个数字是你做成本核算和上下文控制的依据。我在接入后做的第一件事,就是给所有请求打日志,把usage字段完整记录下来——没有这个数据,后面调上下文窗口和预算全凭感觉。

注意:初版日志里务必把model版本号和usage字段一并记下,这决定了后续排查是往模型行为方向找,还是往上下文管理方向找。

3. 用DeepSeek API搭一个本地问答服务:最小可用代码

3.1 从零搭建最小调用:API Key、请求体与返回解析

搭建一个本地问答服务,先不引入任何框架,用Python标准库加requests就能完成最小闭环。这个闭环要看清楚三件事:请求体长什么样、返回结构是什么、错误怎么暴露。以下代码是我常用的最小骨架:

import requests import json API_KEY = "your-api-key" API_URL = "https://your-api-endpoint/v1/chat/completions" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = { "model": "deepseek-chat", "messages": [ {"role": "system", "content": "你是一个熟悉Python的编程助手。"}, {"role": "user", "content": "用python写一个读取csv文件的函数"} ], "temperature": 0.3, "max_tokens": 1024 } try: resp = requests.post(API_URL, headers=headers, json=payload, timeout=30) resp.raise_for_status() data = resp.json() content = data["choices"][0]["message"]["content"] usage = data["usage"] print(content) print(f"prompt_tokens={usage['prompt_tokens']} " f"completion_tokens={usage['completion_tokens']}") except requests.exceptions.Timeout: print("请求超时,请检查网络或增加timeout值") except requests.exceptions.HTTPError as e: print(f"HTTP错误: {e.response.status_code}") print(e.response.text) except (KeyError, IndexError) as e: print(f"返回结构解析失败: {e}") print(resp.text if 'resp' in dir() else "")

这个骨架里最关键的不是请求本身,而是三个防御点:超时设置、状态码检查、返回结构安全解析。requests不设timeout的话,默认会一直挂着,这在生产环境里直接拖垮线程池。30秒是一个起点,取决于你的任务复杂度——纯文本生成可以降到10秒,代码生成建议保留20秒以上。

返回解析里的choices[0].message.content是常用的取值路径,但不要假设这个路径永远存在。DeepSeek在极端情况下会返回空choices或改字段结构。我习惯在解析后用isinstance和len做一次形状校验,宁可解析失败打日志,也不要让json异常直接冒泡到调用方。

3.2 上下文管理:多轮对话的窗口不设,越聊越傻

很多人第一次写多轮对话时,会犯同一个错误:每次请求把历史消息全部堆积进messages数组。这在短期看来没问题,但token消耗会随对话轮数线性上涨,同时模型对远距离信息的关注度衰减,导致“越聊越傻”。

有一个被验证有效的做法,叫滑动窗口裁剪:固定一个最大消息对数量,超过后从最早的消息开始丢弃,只保留system指令和最近的几轮对话。这个方案保留对话连贯性的同时,控制了token成本和输入噪声。

from collections import deque SYSTEM_PROMPT = {"role": "system", "content": "你是AI助手,回答尽量简洁。"} MAX_ROUNDS = 6 # 保留最近6轮用户+助手消息 class ChatMemory: def __init__(self): self.history = deque(maxlen=MAX_ROUNDS) self.system = SYSTEM_PROMPT def add(self, role, content): self.history.append({"role": role, "content": content}) def build_messages(self): return [self.system] + list(self.history) def trim_oldest(self): if len(self.history) == self.history.maxlen: self.history.popleft()

这里的滑动窗口不是简单“截断字符串”,而是按“轮次”做单位裁剪。一个轮次包括一组user消息和对应assistant回复。如果只按消息条数裁剪,很容易出现只有问题没有回答的畸形上下文,模型的回复风格会随之漂移。

窗口大小不是拍脑袋定的。一个参考基准是:任务越依赖前文细节,窗口往大调;任务偏短时问答,4到6轮足够。调窗口的唯一依据是token成本和回答质量的折线——当增加窗口不再带来明显质量提升时,那个临界值就是你的最佳窗口。

3.3 把流式输出接上:体验差一个量级的细节

非流式请求要等模型完整生成后才返回,长答案的等待时间足以让用户以为服务挂了。流式输出把“最终响应”变成“增量序列”,首字延迟能降低一个量级。DeepSeek API的流式请求只需把payload里的stream字段设为true,返回会变成通过SSE分隔的数据块。

import requests import json payload["stream"] = True def stream_chat(payload): resp = requests.post(API_URL, headers=headers, json=payload, stream=True, timeout=60) for line in resp.iter_lines(): if not line: continue line_text = line.decode("utf-8") if line_text.startswith("data:"): data_json = line_text[5:].strip() if data_json == "[DONE]": break try: chunk = json.loads(data_json) delta = chunk["choices"][0]["delta"].get("content", "") print(delta, end="", flush=True) except json.JSONDecodeError: continue

流式模式的解析核心是delta而不是message——每个块里都只有一个增量片段,累加后才构成完整内容。很多从非流式切过来的人会在这一步踩坑,去data里找message字段,找到一半以为协议变了。

flush=True这个参数在流式场景里不是可选优化。Python的print默认换行刷新,但这里我们要的是逐字输出效果,不手动flush的话缓冲区会把内容攒成一段一段蹦出来,体验直接废掉。代码里习惯性带上它,能省很多调试。

流式接口的另一层意义在于:你可以把流式连接断开当作一个信号来用。比如客户端主动取消生成(用户点了停止),立即关闭响应连接,服务端会收到异常并停止计算——这对控制成本很重要,因为流式生成是边算边吐的,取消越早省钱越多。

4. 把DeepSeek私有化:本地部署中你绕不开的三个环节

4.1 模型权重与基础环境:先看硬件预算再谈部署

本地部署的动机一般是两个:数据出域合规要求,或者是对单次调用成本敏感的高频场景。但本地部署不是零成本方案,它的前置条件是硬件预算。跑DeepSeek这类大规模模型需要的不只是显存,还有内存带宽和持续供电。

部署前先做一道数学题:模型权重占多大显存、推理时的KV Cache和激活值还额外要多少。经验算法是权重占用约等于参数量乘2字节(以FP16计算),再预留30%到50%作为运行时余量。也就是说一个7B级别的模型,FP16权重就要约14GB显存,四舍五入是16GB起步。所以显存决定了你模型的规模上限,而不是反过来。

推理框架的选择直接影响效率。当前社区常用的思路是把模型量化到不同精度以匹配硬件:FP16保真度最高,但显存要求也最高;INT8占用减半,损失有限;INT4最省显存,但有些场景下质量下降明显,尤其是生成代码和数字推理类。我个人建议从FP16开始,跑不动的场景再逐级降精度,而不是一开始就上最低档。

本地环境还牵涉一套依赖栈:Python版本、CUDA驱动、推理框架的对应版本。这里最常见的坑是相互兼容问题。一个可行的策略是用虚拟环境把依赖与系统环境隔离,同时锁定框架版本。永远不要用最新的依赖去配旧的模型权重——翻车率极高。

4.2 服务化封装:以兼容API方式托管模型

模型加载起来只是第一步,应用层需要的是一个稳定的服务接口。如果全项目只有你一个人调,启动一个Python脚本直接交互就够了;一旦有多个服务或多人要接,就必须做服务化封装,把模型托管成标准API。

一个常见的做法是把模型加载进程和API服务进程分离。模型进程常驻显存,API服务进程通过本地代理转发请求。这样拆的好处是:API层可以做鉴权、限流、日志,模型层崩溃重启时API层不受影响。很多踩坑案例是两者混在一个进程里,模型推理占用的显存导致API进程内存不足,连健康检查都挂了。

服务化封装要面对的下一个问题是并发。单张显卡的显存是固定的,推理时每路请求都要占一份KV Cache,并发升高到一定程度显存就爆。因此服务化层必须引入信号量或队列控制并发上限,而不是把请求一股脑灌给模型。一个粗略的估计:推理服务的最大并发数不是看CPU核数,而是看显存除以单请求的KV Cache占用。

请求排队策略上,我用过两种方案:一种是让请求等内置返回,需要在前端处理好超时;另一种是先把请求打进来,放入队列后由worker异步消费,调用方轮询结果。后者实现成本高一点,但能避免长时间HTTP连接导致的网关超时,适合服务端需要承载大量长请求的场景。

API兼容层的价值在后续升级时也会体现:模型换版本、换框架、甚至从本地切回云端,应用的调用代码不需要改,只要改配置。这是我在所有接入项目里坚持的一层封装,长期看省下的改造时间远大于初期投入。

4.3 单机部署与并发测试:指标看到什么程度算能用

很多人在本地部署完成后,拿浏览器对话一下,觉得“能聊了”就宣布部署完成。这个判断太早了。对话成功只能证明模型能跑,不能证明服务能扛。验证一个本地部署是否合格,至少要看四个指标:单请求延迟、首字延迟、最大并发数和错误率。

单请求延迟是用户能感知的核心指标。不含流式输出的情况下,一个5B到7B模型在消费级显卡上生成一百个token,通常需要几百毫秒到几秒不等。如果观察到反常延迟,优先排查是不是没有显存足够导致的换页,那是致命伤——模型权重被换到内存后,每一步推理都要做IO交换,性能会陡降十几倍。

并发测试要循序渐进:先单并发跑20次确认稳定性,再逐步增加并发数,每轮统计错误率和延迟分布。重点关注一个现象:并发增加后延迟不是线性增长而是台阶式跳变。这通常意味着触发了显存交换,或者是KV Cache碎片化导致申请耗时变长。

错误率这个指标最容易被忽略。我见过不少部署案例,低并发时一切正常,一旦并发超过某个临界值,开始出现大量空响应或连接重置。这类问题不一定在模型代码里,可能在代理层的连接数限制或线程池配置。性能测试不是验收模型能力,是验收整个系统的承压能力。

注意:并发测试时请留出单独的观察窗口,把模型进程的显存占用和API进程的内存占用分开记录,否则出了问题很难定位是哪个环境撑不住了。

5. 高频翻车现场:接入和部署中五个必踩的坑

5.1 响应慢不是模型慢,是连接池和超时设错了

某次我排查一个接口持续延迟3秒以上的问题,模型推理只花了0.4秒,其余时间全耗在了TCP连接建立和TLS握手上。原因是每次请求都新建requests会话,没有复用连接。HTTP连接池是一个请求基础设置,requests库的Session对象会自动帮你复用底层TCP连接,但很多人图省事直接裸调requests.post。

解决方案也简单:全局维护一个requests.Session实例,设置连接池大小和重试策略。连接池大小要根据你的并发量来定:并发10路的应用,连接池至少配20,留出余量。重试策略里要区分连接错误和超时错误,连接错误可以快速重试,读超时则不要盲目重试——因为服务端可能已经在生成了,重试只是浪费配额。

5.2 上下文开太大,成本和延迟一起失控

当一个对话请求注入一万个token的历史消息时,模型在生成前要先对全部历史做一次预填充计算。这个阶段的耗时与token数量基本成正比,所以上下文越大,首字延迟越高,每轮成本也越高。

翻车现场通常是这样的:开发阶段测试数据量小,感觉不到问题;上线后用户对话轮数一多,接口延迟和账单同时飙升。我处理过一例,把无用历史从8000 token裁剪到1200 token后,延迟从8秒降到1.5秒。

解决思路是分层管理历史:必留的近几轮完整保留,更早的内容做摘要,让模型定期总结一段对话的要点,替换原始消息。这不仅省token,还能提升模型对关键信息的关注度,一举两得。

5.3 输出格式乱:JSON解析失败的高发原因与对策

让DeepSeek输出JSON是高频需求,但直接在提示词里写“请输出JSON”是不够的。模型生成的内容里可能混入解释性文字、Markdown代码块标记,或者自身截断在字符串中间。这些都会导致json.loads直接抛异常。

一个常见的对策是在系统提示词里约定“只输出JSON,不要附加任何说明”,并给出一个具体的JSON示例。但这还不够,因为模型在生成超长JSON时可能被max_tokens截断。更稳妥的方案是分段生成:让模型先输出一个规划纲要,再逐段生成内容,每次校验合法性。

还有一层防御是在代码里做容错解析。去掉Markdown代码块标记、提取第一个{到最后一个}之间的内容、尝试json.loads,失败则做一次修复提示让模型重生成。这类兜底逻辑能大幅提高鲁棒性,代价是一次额外的API调用。

5.4 本地部署显存不足:量化等级不是越低越好

本地部署最常见的翻车是“显存不够”,然后急于把模型量化到最低精度。我见过一个案例:量化到INT4后模型回答质量严重下滑,涉及简单算术时开始出错,最终发现的根本原因是量化校准集与业务语料分布相差太远。

显存不够时先做减法:降低并发数、缩小上下文窗口长度,这能减少KV Cache占用。这两个调整都可以显著腾出显存。还不够再考虑量化,但不要跳级——FP16转INT8是最小损失路径,INT4作为最后手段。每种量化方案都要用业务样本做回归测试,不能只看显存是否够用。

另一个被忽略的点是推理框架的显存预分配设置。有些框架默认会一次性占满全部显存,但这会影响同机其他进程。可以通过环境变量限制缓存比例,让框架按需申请而不是一次性吞掉整张卡。

5.5 回调失败没人知道:重试机制里的死循环陷阱

用API做异步任务时,一个常见设计是任务完成后回调你的业务服务。但回调接口挂掉时,重试机制如果不加熔断,会导致同一个回调请求无限重发,日志里全是一条错误刷屏。

重试的黄金法则是“最多三次,逐次退避”。第一次重试等1秒,第二次等2秒,第三次等4秒。三次都不成功就丢弃或进死信队列,等人工处理。在这个基础上,加一个按IP或任务ID维度的频率限制,防止幂等设计有缺陷时会重复执行有副作用的操作。

如果重试还是把下游打挂,就要在调用侧加熔断开关:当连续失败超过阈值时,停止对该地址发送回调,改为把错误写入本地表,由定时任务扫描重投。这个兜底方案把“实时性”换成了“可靠性”,在业务上能接受的场景里反而是最优解。

6. 让DeepSeek更顺手:一个亲测有效的提示词框架与验证习惯

把DeepSeek用顺手,我的核心习惯是给每个任务建立一套“提示词模板+输出格式约束+验收用例”的最小组合。提示词模板固定任务背景、角色、输出结构;输出格式约束用示例段把期望的答案形状写清楚;验收用例则是一组带预期输出的测试样本,每次改完模板先跑这批用例,再上真实数据。

提示词模板里有一个被低估的字段:system消息里的“任务上下文”。相比user消息里的具体问题,system消息能持续约束模型行为。我会把项目背景、词汇习惯、禁止事项都写在system里,用户侧只留具体问题。这样模型不会在几轮对话后忘记基本规则,输出一致性明显提升。

具体到提示词的写法上,有一个被我专门用来调稳定性的结构:先给角色定义,再给任务步骤,最后给输出格式示例。任务步骤这一步很关键,它引导模型在生成前做分解,降低漏步骤的概率。输出格式示例尽量给出完整JSON或表格形状,模型会模仿这个结构——这比文字描述“要JSON格式”好使得多。

我自己的一个验证习惯是:每个新任务上线前,准备20条覆盖正常、边界、异常三种类型的输入,逐个跑一遍,把结果保存下来。这一批结果就是回归基线。每次修改提示词或切换模型版本时重新跑一遍,比较输出变化。没有这个基线,你只会停留在“感觉变好了”的阶段,永远不会知道改一次提示词到底影响了多少输出。

回看这些踩过的坑,大部分问题不是出在模型能力上,而是出在我把模型当成一个稳定的计算函数来用,却忽略了它的概率本质和工程依赖。现在我的做法是:接入前先建日志和评估集,调试时只动一个变量,上线后盯usage和错误率。这三条习惯帮我提前挡掉了绝大多数问题。如果你正要开始做接入,也建议从这三条入手。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 11:41:50

AnyPS5跨平台串流实战:从编码到网络的全链路优化指南

1. 从“AnyPS5”这个标题说起:一个跨平台串流工具的设计思路第一次看到“AnyPS5”这个标题,我脑子里蹦出来的第一反应是:这大概率是一个围绕主机游戏串流展开的项目,而且“Any”这个前缀暗示了它的核心卖点——跨平台、跨设备、尽…

作者头像 李华
网站建设 2026/10/12 11:41:08

优化BP神经网络的网络安全预测系统实战解析

简介:这份资源围绕基于优化BP神经网络的网络安全预测系统,提供了完整的Python实现与论文文档,适合计算机、电子信息工程及数学专业学生用于课程设计、大作业或毕业设计。压缩包共5个文件,包含2个Python脚本、2个pickle模型参数文件…

作者头像 李华
网站建设 2026/10/12 11:40:29

ThinkPad T400 无线网卡白名单修改实战:绕过104报错

简介:本资源是一份针对ThinkPad T400笔记本的BIOS白名单修改实战指南,面向硬件爱好者、黑苹果玩家及有一定动手能力的Windows/Linux双系统用户,解决因主板白名单限制导致新型无线网卡(如Atheros AR9289)无法识别、系统…

作者头像 李华
网站建设 2026/10/12 11:40:25

Ceph分布式存储实战:从集群部署到RBD块存储挂载全解析

1. Ceph是什么,为什么值得学先说个最直观的感受。Ceph这三个字在分布式存储圈子里出现频率极高,但真正把它搞清楚、能动手部署、能上线跑业务的人,远没有想象中那么多。原因很简单:Ceph的学习曲线是陡峭的,组件多、概念…

作者头像 李华
网站建设 2026/10/12 11:40:12

个人AI助手缓存系统实战:三层混合架构设计与落地

1. 项目概述:为什么一个“个人AI助手”到了阶段3必须上缓存系统?“Agent实践7 - 个人AI助手 - 阶段3 缓存系统搭建”——这个标题里藏着三个关键信号:Agent、个人AI助手、阶段3。它不是从零开始写个聊天框,也不是调用一次API就完事…

作者头像 李华
网站建设 2026/10/12 11:39:57

CSDN 云 IDE 初体验:用 TaoToken 统一 Key 打通 AI 补全与调试链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华