v2: Initial clean branch with unit tests + CI/CD pipeline
CI / build (push) Waiting to run
CI / unit-tests (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions

- Started from ac75faa (initial E4B-MarkBase integration)
- Kept Sources/ (all engine code) + Package.swift + .gitignore
- Removed all ad-hoc tests, documentation, scripts, Python files
- Added Tests/00_Unit/ (MathTest, TokenizerTest, SamplerTest)
- Added .gitea/workflows/ci.yaml (build + unit tests + lint)
- Added Scripts/check_resources.sh (memory-aware test runner)
- Added Tests/Manifest.json (resource requirements for all tests)
- Focus: 4-bit quantized models only
This commit is contained in:
MarkBase Admin
2026-07-05 13:29:25 +08:00
commit 8a66b9086a
90 changed files with 22252 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
import XCTest
@testable import MarkBase
final class MathTest: XCTestCase {
// MARK: - GELU (tanh approximation)
func testGELUZero() {
let result = gelu(0.0)
XCTAssertEqual(result, 0.0, accuracy: 1e-6)
}
func testGELUPositive() {
let result = gelu(1.0)
XCTAssertEqual(result, 0.841192, accuracy: 1e-4)
}
func testGELUNegative() {
let result = gelu(-1.0)
XCTAssertEqual(result, -0.158808, accuracy: 1e-4)
}
func testGELULargePositive() {
let result = gelu(5.0)
XCTAssertEqual(result, 5.0, accuracy: 1e-4)
}
func testGELULargeNegative() {
let result = gelu(-5.0)
XCTAssertEqual(result, 0.0, accuracy: 1e-4)
}
// MARK: - RMS Normalization
func testRMSNormBasic() {
let input: [Float] = [1.0, 2.0, 3.0, 4.0]
let weight: [Float] = [0.5, 0.5, 0.5, 0.5]
let rms = sqrt(input.map { $0 * $0 }.reduce(0, +) / Float(input.count))
let scale: Float = 1.0 / (rms + 1e-6)
let expected = input.map { $0 * scale * 0.5 }
let output = rmsNorm(input: input, weight: weight, eps: 1e-6)
for (o, e) in zip(output, expected) {
XCTAssertEqual(o, e, accuracy: 1e-5)
}
}
func testRMSNormAllZeros() {
let input: [Float] = [0, 0, 0]
let weight: [Float] = [1, 1, 1]
let output = rmsNorm(input: input, weight: weight, eps: 1e-6)
XCTAssertEqual(output, [0, 0, 0])
}
func testRMSNormSingleElement() {
let input: [Float] = [3.0]
let weight: [Float] = [2.0]
let expected: Float = 2.0
let output = rmsNorm(input: input, weight: weight, eps: 1e-6)
XCTAssertEqual(output[0], expected, accuracy: 1e-5)
}
// MARK: - Element-wise Addition
func testEltwiseAdd() {
let a: [Float] = [1, 2, 3]
let b: [Float] = [4, 5, 6]
let result = eltwiseAdd(a: a, b: b)
XCTAssertEqual(result, [5, 7, 9])
}
func testEltwiseAddDifferentLengths() {
let a: [Float] = [1, 2]
let b: [Float] = [3, 4, 5]
let result = eltwiseAdd(a: a, b: b)
XCTAssertEqual(result.count, min(a.count, b.count))
XCTAssertEqual(result, [4, 6])
}
// MARK: - Element-wise Multiplication
func testEltwiseMul() {
let a: [Float] = [1, 2, 3]
let b: [Float] = [4, 5, 6]
let result = eltwiseMul(a: a, b: b)
XCTAssertEqual(result, [4, 10, 18])
}
func testEltwiseMulByZero() {
let a: [Float] = [1, 2, 3]
let b: [Float] = [0, 0, 0]
let result = eltwiseMul(a: a, b: b)
XCTAssertEqual(result, [0, 0, 0])
}
func testEltwiseMulNegative() {
let a: [Float] = [2, -3]
let b: [Float] = [-4, 5]
let result = eltwiseMul(a: a, b: b)
XCTAssertEqual(result, [-8, -15])
}
}
// MARK: - Helper implementations for CPU-based tests
func gelu(_ x: Float) -> Float {
let tanhVal = tanh(0.79788456 * (x + 0.044715 * x * x * x))
return 0.5 * x * (1.0 + tanhVal)
}
func rmsNorm(input: [Float], weight: [Float], eps: Float) -> [Float] {
let count = input.count
let rms = sqrt(input.map { $0 * $0 }.reduce(0, +) / Float(count) + eps)
let scale: Float = 1.0 / rms
return zip(input, weight).map { $0 * scale * $1 }
}
func eltwiseAdd(a: [Float], b: [Float]) -> [Float] {
return zip(a, b).map(+)
}
func eltwiseMul(a: [Float], b: [Float]) -> [Float] {
return zip(a, b).map(*)
}
+96
View File
@@ -0,0 +1,96 @@
import XCTest
@testable import MarkBase
final class SamplerTest: XCTestCase {
var sampler: Sampler!
override func setUp() {
super.setUp()
sampler = Sampler()
}
// MARK: - Top-K Sampling
func testTopKFilter() {
var logits = [Float](repeating: 0, count: 100)
logits[10] = 10.0
logits[20] = 9.0
logits[30] = 8.0
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 3, topP: 1.0)
XCTAssertTrue([10, 20, 30].contains(result),
"Should sample from top-3 tokens (\(result))")
}
func testTopKZero() {
var logits = [Float](repeating: 0, count: 100)
logits[50] = 5.0
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 0, topP: 1.0)
XCTAssertGreaterThanOrEqual(result, 0)
XCTAssertLessThan(result, 100)
}
// MARK: - Temperature
func testTemperatureZeroGreedy() {
var logits = [Float](repeating: -10, count: 1000)
logits[42] = 20.0
let result = sampler.sample(logits: logits, temperature: 0.0, topK: 1, topP: 1.0)
XCTAssertEqual(result, 42, "Temperature 0 should always select highest logit")
}
func testTemperatureHigh() {
var logits = [Float](repeating: 0, count: 1000)
logits[0] = 100.0
let result = sampler.sample(logits: logits, temperature: 10.0, topK: 100, topP: 1.0)
XCTAssertEqual(result, 0, "Overwhelming logit should still be selected")
}
// MARK: - Unused Token Filtering
func testFilterUnusedTokens() {
var logits = [Float](repeating: 0, count: 262144)
logits[258123] = 30.0
logits[500] = 29.0
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 50, topP: 0.95, filterUnusedTokens: true)
XCTAssertLessThan(result, 258000, "Should not sample unused tokens when filtering enabled")
}
func testNoUnusedTokenFilter() {
var logits = [Float](repeating: 0, count: 262144)
logits[258123] = 50.0
logits[500] = 0.0
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 10, topP: 1.0, filterUnusedTokens: false)
XCTAssertEqual(result, 258123, "Should allow unused tokens when filtering disabled")
}
// MARK: - Edge Cases
func testAllSameLogits() {
let logits = [Float](repeating: 1.0, count: 1000)
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 100, topP: 1.0)
XCTAssertGreaterThanOrEqual(result, 0)
XCTAssertLessThan(result, 1000)
}
func testSingleToken() {
let logits: [Float] = [5.0]
let result = sampler.sample(logits: logits, temperature: 1.0, topK: 1, topP: 1.0)
XCTAssertEqual(result, 0)
}
func testExtremeTemperatureZero() {
var logits = [Float](repeating: -100, count: 100)
logits[7] = 50.0
// Greedy: temperature=0, topK=1
let result = sampler.sample(logits: logits, temperature: 0.0, topK: 1, topP: 1.0)
XCTAssertEqual(result, 7)
}
}
+57
View File
@@ -0,0 +1,57 @@
import XCTest
@testable import MarkBase
final class TokenizerTest: XCTestCase {
var tokenizer: Tokenizing!
override func setUp() {
super.setUp()
let modelDir = "/Users/accusys/MarkBaseEngine/models/E4B-MarkBase"
guard FileManager.default.fileExists(atPath: modelDir) else {
return
}
tokenizer = try? TokenizerFactory.load(modelDir: modelDir)
}
func testTokenizerAvailable() {
let modelDir = "/Users/accusys/MarkBaseEngine/models/E4B-MarkBase"
guard FileManager.default.fileExists(atPath: modelDir) else {
throw XCTSkip("E4B-MarkBase model not found")
}
XCTAssertNotNil(tokenizer, "Tokenizer should load successfully")
}
func testEncodeDecodeRoundtrip() throws {
try XCTSkipIf(tokenizer == nil, "Tokenizer not available")
let inputs = ["Hello", "Hello World", "test", "123", "你好"]
for input in inputs {
let tokens = tokenizer.encode(text: input)
let decoded = tokenizer.decode(tokens: tokens)
XCTAssertEqual(decoded.lowercased(), input.lowercased(),
"Roundtrip failed for '\(input)': got '\(decoded)'")
}
}
func testSpacePreservation() throws {
try XCTSkipIf(tokenizer == nil, "Tokenizer not available")
let input = "Hello World"
let tokens = tokenizer.encode(text: input)
let decoded = tokenizer.decode(tokens: tokens)
XCTAssertTrue(decoded.contains(" "), "Spaces should be preserved in '\(decoded)'")
}
func testSpecialTokens() throws {
try XCTSkipIf(tokenizer == nil, "Tokenizer not available")
// BOS token should exist
let bosToken = tokenizer.encode(text: "")
XCTAssertFalse(bosToken.isEmpty, "BOS token should be prepended")
}
func testEmptyString() throws {
try XCTSkipIf(tokenizer == nil, "Tokenizer not available")
let tokens = tokenizer.encode(text: "")
let decoded = tokenizer.decode(tokens: tokens)
XCTAssertNotNil(decoded)
}
}