This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
校对:Portnoy
Wednesday, 12 November 2025
一. 导言导言 导言 导言 导言
欢迎使用 {6!} we'} 已建立完整基础设施的 < -> 接收管道\ MSC3}部分#4), Windows 客户端 @(部分#5* 嵌入和矢量搜索 * * *部分#3),+GPU设置部分#2将本地LLM整合为实际生成写作建议=.}
这是我自己编辑的A )Q MS K3Q 同一声音 同样的实用性 ,Q 一样
我们终于在这里将“"QAI "”作为 “"AI 写作助手 @" [email protected]'}在您的 AZ4000+GPU,产生上下文_-QAWARE建议基于您以往的博客文章<.>#
为什么本地LLM?
在“,”中潜水前让我们来理解我们为什么在本地运行模型而不是使用 OpenAI' API}.吗?
本地对API比较
| @ | _Aspect { | }当地LLM | API MS K3 OpenAI,等 |
|---|---|---|---|
| 隐私隐私权 向第三方发送的数据 * | * | ||
| 费用成本 @ | @#❌_Per@-token定价 | ||
| 时间间隔 ⚠ 网络依赖者 @ | |||
| 自定义 -=YTET -伊甸园字幕组=- 翻译: | |||
| 模式选择 任何 GGUF 型号 @ | @ @ ❌# provider@'#s 型號只有 @MS K5} @ | ||
| 离线 @ | ✅ 工作在离线@ | _❌ 需要互联网 @MS K4 | |
| 设置设置设置 | MS K1 复杂 | I✅ 简单 |
对于写作助理 , 隐私和成本问题 我们不想要寄给外部APIS,的博客草稿
C#的 LLM 集成选项
C#:有几种运行LLMS的方法。
graph TB
A[C# Application] --> B{Integration Method}
B --> C[LLamaSharp]
B --> D[ONNX Runtime]
B --> E[TorchSharp]
B --> F[HTTP API]
C --> G[llama.cpp bindings]
G --> H[GGUF Models]
D --> I[ONNX Models]
I --> J[Limited Model Support]
E --> K[PyTorch Models]
K --> L[Complex Setup]
F --> M[External Process]
M --> N[Ollama, LM Studio]
class C recommended
class G,H llamaSharp
classDef recommended stroke:#333,stroke-width:4px
classDef llamaSharp stroke:#333,stroke-width:2px
我所选择的
为什么?
- 原生的 C#装订为 lama MS K1cpp @ (最快速的推论库@)
- 支持 GGUF 格式“ ( @ modern@ MS K1” 。
- CUDA 加速度建设
- 积极发展和伟大的社区
- 与Llama MSMK0 Mistral , Phi, Gemma @,和更多合作
理解模型格式 * _&_量化
GGUF 格式
GGUF 地面 (GPT- 统一格式MS K2是高效运行LLMs的标准@.
graph LR
A[Original Model<br/>Llama 2 7B<br/>~28GB float32] --> B[Quantization]
B --> C[Q4_K_M<br/>~4.1GB<br/>4-bit]
B --> D[Q5_K_M<br/>~4.8GB<br/>5-bit]
B --> E[Q6_K<br/>~5.5GB<br/>6-bit]
B --> F[Q8_0<br/>~7.2GB<br/>8-bit]
C --> G[Fast, Lower Quality]
D --> H[Balanced]
E --> I[Higher Quality]
F --> J[Near Original]
class A original
class C,D quantized
class H recommended
classDef original stroke:#333,stroke-width:2px
classDef quantized stroke:#333,stroke-width:2px
classDef recommended stroke:#333,stroke-width:2px
所解释的量化:
- 原始型号@: @ @ 32-#bits floors @ I(_ 非常大@ MS K3}非常精确 @ MPK4
- 4:{4-_bit整数}(75% 小}, 最起码质量损失~)
- 最常用病例的甜点
- @ 8: @ near@ - @ 原始品质 @ I,} 仍然在 MS K3#x 小
由硬件选择模型
-=YTET -伊甸园字幕组=- 翻译: |-------|---------------|------------|-----------|------------|------------|---------| | Phi-3 迷你MS K1B) @|Q# 2.3QGB#|Q}(~4G) (|Q) 易如反掌 ∮|Q# | Llama @ 2 @ @ I7_B @|Q#4.1QGB |Q}(6G) (|Q) @MS K5QQ收音机@Tight:|Q} @✅Z Good #|QQ# MS K9QAY 易如反掌 ∮|Q}⭐⭐⭐+好| | 米斯特拉尔 7B @|Q#4.1QGB |Q}(~6G) (|Q) @MS K5QQ收音机@Tight:|Q} @✅Z Good #|QQ# MS K9QAY#简便 @MSC10Q}{⭐⭐⭐⭐#更好的|# | Gemma @ 7_B @|Q#4.1QGB |Q}(~6G) (|Q) @MS K5QQ收音机@Tight:|Q} @✅Z Good #|QQ# MS K9QAY#简便 @MSC10Q}{⭐⭐⭐⭐#更好的|# | Llama @ 3 @ @ I8_B @|Q#4.7QGB |Q}(~7G) (|Q) *超紧的⚠Q}*非常緊密的MS K6Q} <{✅Q}[好,|Q] | Llama @ 2 @ @ I13_B @|Q#7.4QGB |Q}(10GG) (|Q) 没有|QQ #⚠Q#Tight@|Q#好|Z}更好的|+#
GPU: 的建议
- **-=YTET -伊甸园字幕组=- 翻译:**以“:”开头 米斯特拉尔 7B 或 Phi-3迷你 (#安全 )}
- -=YTET -伊甸园字幕组=- 翻译:: Llama @ 3 @ @ I8_B @ (_ 最佳质量@)}或 米斯特拉尔 7B -=YTET -伊甸园字幕组=- 翻译:
- 16+GB VRAM1我设置: Llama @ 3 @ @ I8_B 或尝试 13B模型
- CPU=-}仅@ :_ 任何模型都能工作, @ MPK1} 只是慢得多的“ MS K2” 开始使用 Phi-3 Mini 速度@ )
我的建议建议: 米斯特拉尔 7B @ (_ 最新版本@)} 或 拉拉玛 3 # 8B# (MSKOB)
- 技术写作质量优良
- 在所有 GPU 大小范围内工作
- 足够快,可交互使用
- 善于遵守指示
下载模型
模型在 Hugging face@. @ we'}使用量化的 GGUF 版本=.}
查找 GGUF 模型
- 转到 抱抱脸
- 搜索@: @%
"mistral 7b gguf" - 寻找 Bloke @' @squatization 最受欢迎的 (@)}
直接链接 (=Bloke='=QQQ=):=Bloke=MsK1}字幕校对:
下载特定数量
# Install huggingface-cli
pip install huggingface-hub
# Download Mistral 7B Q5_K_M (recommended)
huggingface-cli download TheBloke/Mistral-7B-Instruct-v0.2-GGUF \
mistral-7b-instruct-v0.2.Q5_K_M.gguf \
--local-dir C:\models\mistral-7b \
--local-dir-use-symlinks False
或下载手动“:”
- 点击“%"” 文件夹和版本@"}标签
- 查找查找查找
mistral-7b-instruct-v0.2.Q5_K_M.gguf万岁,万岁 - 点击下载
- 保存到
C:\models\mistral-7b\
LamaSharrp 设置
安裝 NuGet 软件包
cd Mostlylucid.BlogLLM.Core
dotnet add package LLamaSharp # Latest version
dotnet add package LLamaSharp.Backend.Cuda12 # Latest, matching CUDA version
为什么有两个包? ?
[LLamaSharp](https://github.com/SciSharp/LLamaSharp)-核心图书馆LLamaSharp.Backend.Cuda12- CUDA 哥大 12GPU加速度的二進位
CUDA 后端
LalamaSharp 如果安装正确, 将自动检测 CUDA 是否安装正确@. @%
using LLama;
using LLama.Common;
// Check if CUDA is available
bool cudaAvailable = NativeLibraryConfig.Instance.CudaEnabled;
Console.WriteLine($"CUDA Available: {cudaAvailable}");
如果 false# , 检查#: @
- CUDA 12.x 已安装 @ (Part *2)
LLamaSharp.Backend.Cuda12已安装软件包- PATH 包括 CUDA bin 目录
建立LLLM处
模型参数
using LLama;
using LLama.Common;
namespace Mostlylucid.BlogLLM.Core.Services
{
public class ModelParameters
{
public string ModelPath { get; set; } = string.Empty;
public int ContextSize { get; set; } = 4096; // Context window
public int GpuLayerCount { get; set; } = 35; // Layers on GPU (35 = all for 7B)
public int Seed { get; set; } = 1337; // For reproducibility
public float Temperature { get; set; } = 0.7f; // Creativity (0.0 = deterministic, 1.0 = creative)
public float TopP { get; set; } = 0.9f; // Nucleus sampling
public int MaxTokens { get; set; } = 500; // Max generation length
}
}
参数解释:
环境大小“: ” 这个模型能同时用多少文本来显示“" See"”。
- @ 4096_ 符号@ MS K1_ 3000_ 单词 @ title
- 更大的 {=} 上下文更多, 但越慢, VRAM 就越慢
GpulayerCount 计算器:GPU上有多少变压器层运行
- 7B 型号有 @~32C
- @35 @ @ =# 所有东西都贴在GPU$(#fastest@)}
- 低值@= 使用少 VRAM 但慢
温度:控制随机性
- @0.0 @ @ = @ 总是选择最有可能的牌子 @ I(boring@, @ 重复@ MS K4}
- @0.7 @ @ =# 良好的平衡 @ I( @ 我们的默认=) @
- @1.0+ @ @ = @ 非常有创意 @ I(# 可以是非感官的@) @
顶上P:核取样
- 0.9 MS K1 考量构成概率质量90%的符号
- 防止从非常不可能得到的象征物取样
LLM 服务实施
using LLama;
using LLama.Common;
using Microsoft.Extensions.Logging;
namespace Mostlylucid.BlogLLM.Core.Services
{
public interface ILlmService
{
Task<string> GenerateAsync(string prompt, CancellationToken cancellationToken = default);
Task<string> GenerateWithContextAsync(string prompt, List<string> contextChunks, CancellationToken cancellationToken = default);
}
public class LlmService : ILlmService, IDisposable
{
private readonly LLamaWeights _model;
private readonly LLamaContext _context;
private readonly ILogger<LlmService> _logger;
private readonly ModelParameters _parameters;
public LlmService(ModelParameters parameters, ILogger<LlmService> logger)
{
_parameters = parameters;
_logger = logger;
_logger.LogInformation("Loading model from {ModelPath}", parameters.ModelPath);
// Configure model parameters
var modelParams = new ModelParams(parameters.ModelPath)
{
ContextSize = (uint)parameters.ContextSize,
GpuLayerCount = parameters.GpuLayerCount,
Seed = (uint)parameters.Seed,
UseMemoryLock = true, // Keep model in RAM
UseMemorymap = true // Memory-map the model file
};
// Load model
_model = LLamaWeights.LoadFromFile(modelParams);
_context = _model.CreateContext(modelParams);
_logger.LogInformation("Model loaded successfully. VRAM used: ~{VRAM}GB",
EstimateVRAMUsage(parameters.GpuLayerCount));
}
public async Task<string> GenerateAsync(string prompt, CancellationToken cancellationToken = default)
{
var executor = new InteractiveExecutor(_context);
var inferenceParams = new InferenceParams
{
Temperature = _parameters.Temperature,
TopP = _parameters.TopP,
MaxTokens = _parameters.MaxTokens,
AntiPrompts = new[] { "\n\nUser:", "###" } // Stop generation at these
};
var result = new StringBuilder();
_logger.LogInformation("Generating response for prompt: {Prompt}", TruncateForLog(prompt));
await foreach (var token in executor.InferAsync(prompt, inferenceParams, cancellationToken))
{
result.Append(token);
}
var response = result.ToString().Trim();
_logger.LogInformation("Generated {Tokens} tokens", CountTokens(response));
return response;
}
public async Task<string> GenerateWithContextAsync(
string prompt,
List<string> contextChunks,
CancellationToken cancellationToken = default)
{
// Build prompt with retrieved context
var fullPrompt = BuildContextualPrompt(prompt, contextChunks);
_logger.LogInformation("Context chunks: {Count}, Total prompt tokens: ~{Tokens}",
contextChunks.Count, CountTokens(fullPrompt));
return await GenerateAsync(fullPrompt, cancellationToken);
}
private string BuildContextualPrompt(string userPrompt, List<string> contextChunks)
{
var sb = new StringBuilder();
sb.AppendLine("You are a helpful writing assistant for a technical blog.");
sb.AppendLine("Use the following excerpts from past blog posts as context:");
sb.AppendLine();
for (int i = 0; i < contextChunks.Count; i++)
{
sb.AppendLine($"--- Context {i + 1} ---");
sb.AppendLine(contextChunks[i]);
sb.AppendLine();
}
sb.AppendLine("---");
sb.AppendLine();
sb.AppendLine("Based on the context above, help with the following:");
sb.AppendLine(userPrompt);
sb.AppendLine();
sb.AppendLine("Response:");
return sb.ToString();
}
private int CountTokens(string text)
{
// Rough estimate: 1 token ≈ 4 characters
return text.Length / 4;
}
private string TruncateForLog(string text, int maxLength = 100)
{
if (text.Length <= maxLength) return text;
return text.Substring(0, maxLength) + "...";
}
private double EstimateVRAMUsage(int gpuLayers)
{
// Rough estimate for 7B model
return (gpuLayers / 35.0) * 6.0; // ~6GB for full 7B model
}
public void Dispose()
{
_context?.Dispose();
_model?.Dispose();
}
}
}
如何运作:
- 模型加载使用指定参数将 GGUF 模型装入 VRAM
- 交互式执行器@ :LalamaSharp' 用于聊天的处决模式@-类似互动
- 推断合成器:流标语, 它们生成了 @(@real}-}时间输出@)}
- 环境建设\ :\ 用已取回的部落格块组合用户提示@ info
- 反-: 停止在一定的字符串中生成 {(} 防止乱七八糟_)
测试服务处
using Microsoft.Extensions.Logging;
class Program
{
static async Task Main(string[] args)
{
// Setup logging
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<LlmService>();
// Configure model
var parameters = new ModelParameters
{
ModelPath = @"C:\models\mistral-7b\mistral-7b-instruct-v0.2.Q5_K_M.gguf",
ContextSize = 4096,
GpuLayerCount = 35,
Temperature = 0.7f,
MaxTokens = 200
};
// Create service
using var llmService = new LlmService(parameters, logger);
// Test simple generation
Console.WriteLine("=== Test 1: Simple Generation ===\n");
var response1 = await llmService.GenerateAsync(
"Explain what Docker Compose is in 2-3 sentences."
);
Console.WriteLine(response1);
Console.WriteLine("\n");
// Test with context
Console.WriteLine("=== Test 2: Generation with Context ===\n");
var context = new List<string>
{
"Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services.",
"In development, Docker Compose makes it easy to spin up all dependencies (databases, caches, etc.) with one command: docker-compose up."
};
var response2 = await llmService.GenerateWithContextAsync(
"Write an introduction paragraph for a blog post about using Docker Compose for development dependencies.",
context
);
Console.WriteLine(response2);
}
}
预期成果产出:
=== Test 1: Simple Generation ===
Docker Compose is a tool that allows you to define and run multi-container Docker applications using a simple YAML configuration file. It simplifies the process of managing multiple containers, networking, and volumes, making it ideal for development environments.
=== Test 2: Generation with Context ===
If you've ever found yourself juggling multiple terminal windows to start databases, caches, and other services for local development, Docker Compose is about to become your new best friend. This powerful tool lets you define your entire development environment in a single YAML file and spin everything up with one command. In this post, we'll explore how to leverage Docker Compose to manage all your development dependencies, making your local setup reproducible, shareable, and incredibly easy to manage.
该模型正在发挥作用,并产生一致的@,上下文 @-aver text_.
与建议小组结合
现在让我们将LLM 生成整合到我们的 Windows 客户端中,
更新建议服务
namespace Mostlylucid.BlogLLM.Client.Services
{
public class SuggestionService : ISuggestionService
{
private readonly BatchEmbeddingService _embeddingService;
private readonly QdrantVectorStore _vectorStore;
private readonly ILlmService _llmService; // NEW
public SuggestionService(
BatchEmbeddingService embeddingService,
QdrantVectorStore vectorStore,
ILlmService llmService) // NEW
{
_embeddingService = embeddingService;
_vectorStore = vectorStore;
_llmService = llmService;
}
public async Task<string> GenerateAiSuggestionAsync(
string currentText,
List<SimilarPost> context)
{
// Extract text from similar posts
var contextChunks = context
.Take(3) // Top 3 most similar
.Select(p => p.FullText)
.ToList();
// Determine what type of suggestion to generate
var prompt = DeterminePromptType(currentText);
// Generate suggestion
var suggestion = await _llmService.GenerateWithContextAsync(
prompt,
contextChunks
);
return suggestion;
}
private string DeterminePromptType(string currentText)
{
// Analyze what user is writing
var lines = currentText.Split('\n');
var lastLine = lines.LastOrDefault(l => !string.IsNullOrWhiteSpace(l)) ?? "";
// Is user starting a new section?
if (lastLine.StartsWith("## "))
{
return "Suggest 3-5 bullet points for what this section could cover.";
}
// Is user writing code?
if (lastLine.Contains("```"))
{
return "Suggest relevant code examples that might be useful here.";
}
// Is user writing an introduction?
if (currentText.Length < 500 && currentText.Contains("## Introduction"))
{
return "Suggest 2-3 sentences to continue this introduction based on similar posts.";
}
// Default: continue current thought
return "Suggest 1-2 sentences to continue the current paragraph in a natural way.";
}
}
}
更新建议查看模式
public partial class SuggestionsViewModel : ViewModelBase
{
[RelayCommand]
private async Task RegenerateSuggestion()
{
IsGenerating = true;
AiSuggestion = "Generating...";
try
{
var currentText = GetCurrentEditorText(); // From messaging
var suggestion = await _suggestionService.GenerateAiSuggestionAsync(
currentText,
SimilarPosts.ToList()
);
AiSuggestion = suggestion;
}
catch (Exception ex)
{
AiSuggestion = $"Error: {ex.Message}";
}
finally
{
IsGenerating = false;
}
}
}
业绩优化
模型缓存
保持模型在请求之间装入@: @%
public class LlmServiceFactory
{
private static LlmService? _instance;
private static readonly object _lock = new();
public static LlmService GetInstance(ModelParameters parameters, ILogger<LlmService> logger)
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new LlmService(parameters, logger);
}
}
}
return _instance;
}
}
KV 缓存再使用
LamaSharp支持 KV 缓存再利用,
public class StatefulLlmService
{
private readonly InferenceParams _defaultParams;
private string _cachedPromptPrefix = string.Empty;
public async Task<string> GenerateWithPrefixAsync(string prefix, string newPrompt)
{
// If prefix matches cached, reuse KV cache
if (prefix == _cachedPromptPrefix)
{
// Only process new tokens
return await GenerateAsync(newPrompt);
}
// Process entire prompt and cache
_cachedPromptPrefix = prefix;
return await GenerateAsync(prefix + newPrompt);
}
}
这对我们的用例“ -” 特别有用, 上下文块保持相同@ MS K1 @ 只有用户\ ' @ s question changes ask change+.}
条纹
对于多个建议“,批量”、“:”
public async Task<List<string>> GenerateBatchAsync(List<string> prompts)
{
var results = new List<string>();
foreach (var prompt in prompts)
{
// With KV cache reuse, subsequent prompts are faster
results.Add(await GenerateAsync(prompt));
}
return results;
}
书写协助快速工程
这是不同情景的模板@: @%
继续写入
private string PromptContinueWriting(string currentText, List<string> context)
{
return $@"You are a technical blog writing assistant.
Here are excerpts from similar blog posts:
{string.Join("\n\n", context.Select((c, i) => $"--- Post {i + 1} ---\n{c}"))}
Current draft:
{currentText}
Task: Suggest 2-3 sentences to naturally continue the current paragraph.
Keep the same technical depth and casual, pragmatic tone.
Suggestion:";
}
建议款次结构
private string PromptSectionStructure(string sectionTitle, List<string> context)
{
return $@"You are a technical blog writing assistant.
Similar sections from past posts:
{string.Join("\n\n", context)}
New section: {sectionTitle}
Task: Suggest 4-6 bullet points for what this section should cover.
Format as a markdown list.
Bullets:";
}
守则示例
private string PromptCodeExample(string description, List<string> context)
{
return $@"You are a C# coding assistant.
Relevant code from past posts:
{string.Join("\n\n", context)}
Task: {description}
Provide a clean, well-commented C# code example.
Code:";
}
记忆管理
拥有大型号的, 记忆管理是关键
监视 VRAM 使用情况
public class VramMonitor
{
[DllImport("nvml.dll")]
private static extern int nvmlDeviceGetMemoryInfo(IntPtr device, ref NvmlMemory memory);
[StructLayout(LayoutKind.Sequential)]
public struct NvmlMemory
{
public ulong Total;
public ulong Free;
public ulong Used;
}
public static (ulong used, ulong total) GetVramUsage()
{
// Simplified - actual implementation needs proper NVML initialization
var memory = new NvmlMemory();
// nvmlDeviceGetMemoryInfo(device, ref memory);
return (memory.Used / 1024 / 1024, memory.Total / 1024 / 1024); // Convert to MB
}
}
非激活时卸载模型
public class LlmServiceWithUnload : IDisposable
{
private LlmService? _service;
private readonly Timer _unloadTimer;
private DateTime _lastUsed;
public LlmServiceWithUnload()
{
_unloadTimer = new Timer(CheckForUnload, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
private void CheckForUnload(object? state)
{
if (_service != null && (DateTime.Now - _lastUsed) > TimeSpan.FromMinutes(10))
{
_service.Dispose();
_service = null;
GC.Collect();
Console.WriteLine("Model unloaded due to inactivity");
}
}
public async Task<string> GenerateAsync(string prompt)
{
_lastUsed = DateTime.Now;
if (_service == null)
{
// Reload model
_service = CreateService();
}
return await _service.GenerateAsync(prompt);
}
}
错误处理
LLMs 可能以意外的方式失败 @. @ handle 优雅地@: @%
public async Task<string> GenerateWithRetryAsync(string prompt, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await GenerateAsync(prompt);
}
catch (OutOfMemoryException)
{
_logger.LogWarning("OOM error, reducing max tokens");
_parameters.MaxTokens = Math.Max(100, _parameters.MaxTokens / 2);
}
catch (Exception ex)
{
_logger.LogError(ex, "Generation failed, attempt {Attempt}/{Max}", i + 1, maxRetries);
if (i == maxRetries - 1) throw;
await Task.Delay(1000 * (i + 1)); // Exponential backoff
}
}
throw new Exception("Generation failed after retries");
}
摘要摘要
我们成功整合了本地LLM推论 :
- @ ✅ chose 拉马沙尔普 C#集成
- 明白了吗? GGUF 格式 和量化
- @✅ 选定合适的模型 @#(米斯特拉尔 7B / 拉拉玛 3 # 8B##
- ✅ 已使用 Llm Service 加速 CUDA
- ✅集成到 Windows 客户端以征求建议
- “✅”为写作任务进行了即时工程。
- @✅ 添加的性能优化@(caching,批量}#)
- ✅经手的存储管理和错误
什么是' 下一步 @?
内 内容生成 *&}快速工程@, @ we'#Lall 聚焦于完整内容生成管道@:}
- 先进即速工程技术
- 用于迭接精细化的多@ - @ turn 对话框
- 背景窗管理战略
- 质量评价和过滤
- 执行样式一致性
- 处理代码区块生成
- 真实-世界使用模式
我们的'#会让这个系统 真正用于日报博客写作_!}
系列导航
- 部分: 1:_ 导言 _&建筑
- 部分@2:GPU 设置 @&CUDA 在 CQ#
- “3: 理解嵌入器”部分
- 建立接收管道
- 窗口客户端
- 6:地方LLM整合部分 校对:Soup
- 内容生成 *&}快速工程
- 生产部署
资源资源资源 资源和资源资源资源
回头见 部分#7!