Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ let package = Package(
.executableTarget(
name: "IssueTriageDemo", dependencies: ["FluidUse"], exclude: ["README.md", "fetch-issues.sh"]),
.executableTarget(name: "Vela2Check", dependencies: ["FluidUse"]),
.executableTarget(name: "Vela3Check", dependencies: ["FluidUse"]),
.executableTarget(name: "EvokeCheck", dependencies: ["FluidUse"]),
.executableTarget(
name: "EvokeSearchDemo", dependencies: ["FluidUse"], exclude: ["README.md", "fetch-posts.py", "demo.sh"]),
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,35 @@ PII-masked before sending, replies checked against a source document) and
[Sources/FrontDoorDemo](Sources/FrontDoorDemo/README.md) (1,000 synthetic chatbot messages screened and routed at ~90
messages per second: guard on the Neural Engine, route + PII on the GPU).

## Vela 3.0 decisions and spans

`Vela3Manager` runs [vela3-fast](https://huggingface.co/vllm-sr/vela3-fast) (Vela 3.0, vLLM Semantic Router × KR Labs;
UltraBERT-380M encoder, Apache-2.0) on Core ML: decisions over options you name (choice or yes / no about a state), and
the two label sets of the release's `span_sets` adapter, personal information (17 types) and the words of an answer its
context does not support. One encoder pass per question on the GPU (~10 ms for a decision, ~18 ms for a PII pass over a
paragraph; the Neural Engine is slower for this encoder at every length). Rendering, readout, thresholds and the span
decoder reproduce the release's own code: against the Python runtime on the same encoders, 0 token mismatches on 23
Unicode-heavy texts, 0 / 120 decisions and 0 / 16 span sets differ, including windowed inputs
(`swift run -c release Vela3Check parity …`). One pass holds 1,024 tokens: a longer decision throws `tooLong`; for spans the
context of an unsupported-claim check is cut first, then long text is read in overlapping windows (the release's rule).
The pinned snapshot downloads from
[FluidInference/vela-3.0-coreml](https://huggingface.co/FluidInference/vela-3.0-coreml) on first use (~1.48 GB: two
multifunction packages, macOS 15 / iOS 18).

```swift
let vela = try await Vela3Manager.load(from: try await Vela3ModelStore.ensure())
let r = try await vela.decide(
state: "My card was charged twice and I need this fixed today.",
questions: [
.choice(id: "intent", instructions: "What does the customer want?",
options: [("refund", "Money back for the duplicate charge"), ("cancel", "Cancel the order")]),
.yesNo(id: "urgent", instructions: "The customer needs a fast response."),
])
print(r["intent"]!.answer, r["urgent"]!.yes!)
print(try await vela.pii("Hi, I'm Dana Whitfield, dana@example.com.").spans.map { "\($0.label): \($0.text)" })
print(try await vela.unsupported(context: "Adults may take up to 4 g a day.", answer: "Adults may take up to 6 g a day.").spans.map(\.text))
```

## d1-omni-600M decisions and moderation

`D1OmniManager` runs the text path of Liquid AI's [d1-omni-600M](https://huggingface.co/LiquidAI/d1-omni-600M)
Expand Down
27 changes: 22 additions & 5 deletions Sources/FluidUse/Vela2/Vela2Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,29 @@ struct Vela2Heads: Sendable {
w[name] = Array(f[offset..<(offset + count)])
}
}
guard let n = w["norm.weight"], let p = w["w_o.bias"] else { throw Vela2Error.invalidAsset("heads.bin") }
self.hidden = n.count
self.proj = p.count
try self.init(weights: w)
}

/// Heads from named fp32 tensors (Vela 3 ships them as safetensors). `span: false` is a decision-only head set
/// (no `w_t` / `w_e` / `log_tau_span`), which is never asked for span logits.
init(weights w: [String: [Float]], span: Bool = true) throws {
guard let n = w["norm.weight"], let p = w["w_o.bias"], let logTau = w["log_tau"]?.first else {
throw Vela2Error.invalidAsset("readout heads are missing norm / w_o / log_tau")
}
let h = n.count, d = p.count
var shapes: [(String, Int)] = [
("norm.bias", h), ("w_o.weight", d * h), ("w_q.weight", d * h), ("w_q.bias", d), ("w_p.weight", d * h), ("w_p.bias", d),
("cls_mlp.0.weight", 2 * h * h), ("cls_mlp.0.bias", 2 * h), ("cls_mlp.3.weight", 2 * h), ("cls_mlp.3.bias", 1),
]
if span { shapes += [("w_t.weight", d * h), ("w_t.bias", d), ("w_e.weight", d * h), ("w_e.bias", d), ("log_tau_span", 1)] }
for (name, count) in shapes where w[name]?.count != count {
throw Vela2Error.invalidAsset("readout head \(name) is missing or has \(w[name]?.count ?? 0) values, expected \(count)")
}
self.hidden = h
self.proj = d
self.w = w
self.tau = exp(w["log_tau"]![0])
self.tauSpan = exp(w["log_tau_span"]![0])
self.tau = exp(logTau)
self.tauSpan = span ? exp(w["log_tau_span"]![0]) : 1
}

private func layerNorm(_ x: ArraySlice<Float>) -> [Float] {
Expand Down
Loading
Loading