Skip to content
Closed
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
26 changes: 26 additions & 0 deletions test/fixtures/litert_lm_v1_env_dir_embedding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Creates a LiteRT-LM embedding session through the default (environment
// directory) runtime route; args: <abi name> <model path> <cache dir>.
import 'dart:ffi';
import 'dart:io';

import 'package:llamadart/src/backends/litert_lm/litert_lm_runtime.dart';

Future<void> main(List<String> args) async {
debugLiteRtLmAbiOverride = Abi.values.singleWhere((abi) => '$abi' == args[0]);
final client = LiteRtLmRuntimeClient();
try {
final session = await client.createEmbeddingSession(
modelPath: args[1],
backend: 'cpu',
cacheDir: args[2],
maxInputLength: 1024,
numberOfThreads: 2,
);
session.dispose();
stdout.writeln('ENV_DIR_EMBEDDING created');
} on Object catch (error) {
stdout.writeln('ENV_DIR_EMBEDDING ${error.runtimeType}: $error');
} finally {
client.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,11 @@ void main() {
test(
'embedding deadline throws a typed error and cancels native work before reuse',
() async {
// The deadline also bounds the reuse call, which must not race it on a
// loaded host.
const deadline = Duration(seconds: 1);
final (backend, _, context) = await backendForTest(
embeddingTimeout: const Duration(milliseconds: 100),
embeddingTimeout: deadline,
);
mode(11);
final timedOut = expectLater(
Expand Down
28 changes: 24 additions & 4 deletions test/unit/backends/litert_lm/litert_lm_gpu_adapter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ void main() {
expect(vulkan.destroyedInstances, 1);
});

test('reads the count the second call rewrites', () {
vulkan.devices = [('llvmpipe', 4), ('NVIDIA L4', 2), ('gone', 2)];
vulkan.writtenDevices = 2;

final adapters = vulkan.loader.listAdapters();

expect(adapters.unavailableReason, isNull);
expect(
[for (final device in adapters.devices!) device.name],
['llvmpipe', 'NVIDIA L4'],
);
expect(vulkan.destroyedInstances, 1);
});

test('lists no device when the loader reports none', () {
final adapters = vulkan.loader.listAdapters();

Expand Down Expand Up @@ -156,6 +170,10 @@ final class _FakeVulkan {
/// The count the first call reports when it is below [devices], as when a
/// device appears between the two calls.
int? countedDevices;

/// The devices the second call writes and reports with `VK_SUCCESS` when
/// fewer than the first count, as when a device disappears between calls.
int? writtenDevices;
int? createInfoStructureType;
int destroyedInstances = 0;

Expand All @@ -172,18 +190,20 @@ final class _FakeVulkan {
count.value = countedDevices ?? devices.length;
return 0;
}
final written = count.value < devices.length
? count.value
: devices.length;
final available = writtenDevices ?? devices.length;
final written = count.value < available ? count.value : available;
for (var index = 0; index < written; index++) {
final handle = calloc<Uint8>()..value = index;
_handles.add(handle);
handles[index] = handle.cast();
}
count.value = written;
return written < devices.length ? 5 : 0;
return written < available ? 5 : 0;
},
getPhysicalDeviceProperties: (device, properties) {
if (device == nullptr) {
throw StateError('Read an unwritten device handle.');
}
final (name, type) = devices[device.cast<Uint8>().value];
final bytes = properties.cast<Uint8>();
(bytes + 16).cast<Int32>().value = type;
Expand Down
1 change: 1 addition & 0 deletions test/unit/backends/litert_lm/litert_lm_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3595,6 +3595,7 @@ void main() {
(error) => error.message,
'message',
allOf(
contains('LiteRT-LM GPU on ${liteRtLmGateAbi()} needs'),
contains('needs a hardware Vulkan device'),
contains('"llvmpipe (LLVM 20.1.2, 256 bits)"'),
contains('VK_PHYSICAL_DEVICE_TYPE_CPU'),
Expand Down
64 changes: 64 additions & 0 deletions test/unit/backends/litert_lm/litert_lm_v1_embedding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:llamadart/src/backends/litert_lm/litert_lm_service.dart';
import 'package:llamadart/src/backends/litert_lm/litert_lm_v1_runtime.dart';
import 'package:test/test.dart';

import '../../../support/dart_script_snapshot.dart';
import '../../../support/suite_system_temp.dart';
import 'litert_lm_embedding_test.dart' as fixtures;

Expand Down Expand Up @@ -380,6 +381,69 @@ void main() {
expect(count('call_count'), before);
});
}
group('LLAMADART_LITERT_LM_LIB_DIR route', () {
late String script;
setUpAll(
() async => script = await compileDartScript(
'test/fixtures/litert_lm_v1_env_dir_embedding.dart',
),
);

Future<String> createFromEnvironmentDirectory(
String runtimePath,
Abi abi,
) async {
final runtime = Directory(
'${suite().path}/env-dir-${runtimePath.hashCode}',
)..createSync();
final primary = liteRtLmPrimaryLibraryForAbi(Abi.current())!;
File(runtimePath).copySync('${runtime.path}/$primary');
final stub = File('${runtime.path}/stub.c')
..writeAsStringSync('int fixture_companion_stub(void) { return 1; }\n');
final stubLibrary = '${runtime.path}/stub.bin';
final built = await Process.run('cc', [
'-shared',
'-fPIC',
stub.path,
'-o',
stubLibrary,
]);
expect(built.exitCode, 0, reason: '${built.stderr}');
for (final name in liteRtLmRequiredLibrariesForAbi(
Abi.current(),
).where((name) => name != primary)) {
File(stubLibrary).copySync('${runtime.path}/$name');
}
final result = await Process.run(
Platform.resolvedExecutable,
[script, '$abi', model, '${runtime.path}/cache'],
environment: {
'LLAMADART_LITERT_LM_LIB_DIR': runtime.path,
'TMPDIR': suite().path,
},
);
expect(result.exitCode, 0, reason: '${result.stdout}\n${result.stderr}');
return '${result.stdout}';
}

for (final abi in [Abi.linuxArm64, Abi.linuxX64, Abi.windowsX64]) {
test('$abi refuses an unmarked runtime selected by the environment '
'directory', () async {
final output = await createFromEnvironmentDirectory(unmarkedPath, abi);
expect(output, contains('LlamaUnsupportedException'));
expect(output, contains('native embeddings on $abi'));
expect(output, contains('does not export'));
});
}

test('linux_x64 admits a marked runtime selected by the environment '
'directory', () async {
expect(
await createFromEnvironmentDirectory(libraryPath, Abi.linuxX64),
contains('ENV_DIR_EMBEDDING created'),
);
});
});
for (final abi in [Abi.macosArm64, Abi.iosArm64, Abi.androidArm64]) {
test(
'$abi creates embeddings without the in-process LiteRT marker',
Expand Down
Loading