forked from zhongkaifu/TensorSharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSpeculativeDraftHeadLoader.cs
More file actions
142 lines (134 loc) · 5.84 KB
/
Copy pathSpeculativeDraftHeadLoader.cs
File metadata and controls
142 lines (134 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (c) Zhongkai Fu. All rights reserved.
// https://github.com/zhongkaifu/TensorSharp
//
// This file is part of TensorSharp.
//
// TensorSharp is licensed under the BSD-3-Clause license found in the LICENSE file in the root directory of this source tree.
//
// TensorSharp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the BSD-3-Clause License for more details.
using System;
using System.IO;
using TensorSharp.Runtime.Speculative;
namespace TensorSharp.Models
{
/// <summary>
/// Attaches SPECULATOR WEIGHTS that ship as their own file to the target
/// model that was just loaded — layer 3 of the speculative stack, and the
/// one part that is unavoidably model-specific.
///
/// Most checkpoints carry their drafter inside the trunk GGUF (Qwen 3.6 and
/// GLM-5.2 embed a NextN block), so nothing happens here. Gemma 4's draft
/// head ships separately as <c>gemma4-assistant</c> and has to be loaded
/// onto the target before <see cref="IDraftHead.HasDraftHead"/> turns on.
///
/// Shared by the CLI and the server so a <c>--spec-draft-model</c> means the
/// same thing in both. It used to live only in the server, which made the
/// CLI accept the flag and silently ignore it.
/// </summary>
public static class SpeculativeDraftHeadLoader
{
/// <summary>
/// Path of the separate draft-head GGUF the operator configured, or null.
/// </summary>
public static string ConfiguredDraftHeadPath()
{
string path = Environment.GetEnvironmentVariable(SpeculationEnvVars.DraftModel);
if (string.IsNullOrWhiteSpace(path))
path = Environment.GetEnvironmentVariable(SpeculationEnvVars.LegacyDraftModel);
return string.IsNullOrWhiteSpace(path) ? null : path;
}
/// <summary>
/// Load the configured draft-head GGUF onto <paramref name="model"/>.
/// Returns true when there was nothing to do or the head attached
/// successfully; false with <paramref name="error"/> set to an
/// operator-facing explanation otherwise. Never throws: a drafter is an
/// optimization, and failing to attach one must degrade to plain
/// decoding rather than fail the load.
/// </summary>
public static bool TryAttachConfiguredDraftHead(ModelBase model, out string error)
{
error = null;
string draftPath = ConfiguredDraftHeadPath();
if (draftPath == null)
return true;
if (!File.Exists(draftPath))
{
error = $"Draft-head model file not found: {draftPath}";
return false;
}
// A DFlash / DFlash2 drafter is architecture-agnostic on this side: any
// target that can tap the residuals its encoder reads can host one, and
// the file says which it is. --draft-model may already have attached it
// during construction, in which case there is nothing to do.
if (IsDFlashDrafter(draftPath))
{
if (model == null)
{
error = "No model is loaded to attach a DFlash drafter to.";
return false;
}
if (model.HasDFlash)
return true;
try
{
model.LoadDFlashDraftWeights(draftPath);
}
catch (Exception ex)
{
error = $"Failed to load DFlash drafter '{Path.GetFileName(draftPath)}': {ex.Message}";
return false;
}
if (!model.HasDFlash)
{
error = $"DFlash drafter '{Path.GetFileName(draftPath)}' loaded but is incomplete "
+ "(required draft tensors missing).";
return false;
}
return true;
}
if (model is not Gemma4Model gemma4)
{
// A draft GGUF was named but this architecture does not consume a
// separate draft file (Qwen 3.6 embeds its NextN block in the
// trunk). Say so rather than leave the operator wondering why
// their flag was ignored.
error = $"--spec-draft-model was given but the loaded model architecture "
+ $"'{model?.Config?.Architecture ?? "unknown"}' does not use a separate draft GGUF.";
return false;
}
try
{
gemma4.LoadMtpDraftWeights(draftPath);
}
catch (Exception ex)
{
error = $"Failed to load draft head '{Path.GetFileName(draftPath)}': {ex.Message}";
return false;
}
if (!gemma4.HasDraftHead)
{
error = $"Draft head '{Path.GetFileName(draftPath)}' loaded but is incomplete "
+ "(required draft tensors missing).";
return false;
}
return true;
}
/// <summary>True when the file at <paramref name="path"/> declares itself a
/// DFlash drafter. Read from the GGUF rather than inferred from the name:
/// the same flag also names MTP-only assistant files.</summary>
private static bool IsDFlashDrafter(string path)
{
try
{
using var probe = new GgufFile(path);
return string.Equals(probe.GetString("general.architecture"),
DFlashConfig.ArchName, StringComparison.Ordinal);
}
catch
{
return false;
}
}
}
}