Repository: oneapi-src/unified-memory-framework (language: python)

Issue:
Backward compatibility for configs of pools/providers

## Rationale

UMF should provide backward compatible interfaces.

## Description

### Background

MPI team experienced the issue after the PR #692 extends `level_zero_memory_provider_params_t` config structure. The root cause of the issue relates to how MPI instantiate/initialize the L0 provider config. They did the following:
```c
level_zero_memory_provider_params_t l0_config = {
    .level_zero_context_handle = hContext;
    .level_zero_device_handle = hDevice;
    .memory_type = UMF_MEMORY_TYPE_DEVICE;
};

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *l0_ops = umfLevelZeroMemoryProviderOps();
umfMemoryProviderCreate(l0_ops, &l0_config, &hProvider);
```

After PR #692 the code above start crashing because two new fields were added to the `level_zero_memory_provider_params_t` data structure and the example above does not initialize these new fields:
```c
typedef struct level_zero_memory_provider_params_t {
    ze_context_handle_t level_zero_context_handle;
    ze_device_handle_t level_zero_device_handle;
    umf_usm_memory_type_t memory_type;

    // New fields
    ze_device_handle_t *resident_device_handles;
    uint32_t resident_device_count;
} level_zero_memory_provider_params_t;
```

A quick fix for the issue was to init the config data structure with `0` and than assign the required fields:
```c
level_zero_memory_provider_params_t l0_config = { 0 }; // zero-initialize
l0_config.level_zero_context_handle = hContext;
l0_config.level_zero_device_handle = hDevice;
l0_config.memory_type = UMF_MEMORY_TYPE_DEVICE;
```

### Open Questions

The quick fix above works only because it is OK to init fields of the `level_zero_memory_provider_params_t` structure with zeroes. But there are 2 related major question we should address:

#### 1. How to initialize the configs with default values?

In general case, not every field of the config data structure could/should be initialized with zeroes.

#### 2. How to support backward compatibility?

Config data structures are defined in the headers. If application was built with old version of UMF but on the system there is a newer version than even the application initialize all config's fields properly it initializes only fields that exists in the old version of UMF.
Consider an example. There is a `provider_foo` and corresponding `foo_config_t` structure that contains an int field in the 1st version:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal sizeof(int) that is 4 bytes

    assert(foo_params->field1 == 0);
}

// application code:
int main() {
    foo_params_t foo_params = {0};

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

Now in the 2nd version of UMF we extend the `foo_params_t` structure with additional field:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal 2*sizeof(int) that is 8 bytes

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0); // ERROR: if application was compiled with 1st version of the UMF lib then the size of memory pointed by `params` is 4 bytes
}
```

## Possible API Changes

### Option 1: handle-based approach
Do not expose the config structure in interfaces. The config object is allocated inside libumf.so and handle to the config is returned to the client code. Setter/getter APIs are used to setup config parameters. For example:

```c
foo_params_handle_t foo_params = umfCreateFooParams();
umfFooParamsSetField1(foo_params, 7);

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
umfMemoryProviderCreate(foo_ops, foo_params, &hProvider);
```

Client code does not depend on the layout of the `foo_params_t` structure only UMF knows it and can change in different versions.

### Option 2: explicitly pass the size of params 

Data structures that define configs for the pools/providers remains in headers (the same as today). The new fields can be added only to the end. Application explicitly passes the size of the config data structure. Provider/pool implementation determine the version of the config based on the size. To init the config data structure we need to introduce a special macros or header-based inline functions. For example:

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
     params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    if(sizeof(foo_params_t) == params_size) {
         // current version, just copy input params as is.
        foo_params = *(foo_params_t*)params;
    } else {
        // old version, copy only first `params_size` bytes and keep the rest default initialized
        memcpy(& foo_params, params, params_size);
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, sizeof(foo_params_t), &hProvider);
}
```

### Option 3: store version in the params data structure

Similar to Option 2, but instead of using the size of params data structure to determine the version store it explicitly as a first field in the params data structure.

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int version;
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 1;
    params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 2;
    params->field1 = 0;
    params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    int params_version = *(int *)params;

    switch(params_version) {
        case 1:
            ...
            break;
        case 2:
            ...
            break;
        default:
            LOG_ERR("Wrong version");
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

### Design Considerations

I prefer Option 1 over Option 2 and Option 3. I think it provides best ABI compatibility. Another advantage is that the config data structure always initialized when user calls `umfCreateFooParams()`. There is no way to get non-initialized fields in the config, while in case of Option 2 or 3 user might forget to call `umfFooParamsInit(&params)` function.

Between Options 2 and 3, I prefer Option 3 because of two things:

- it stores version explicitly.
- the `umfMemoryProviderCreate` API remains unchanged.

## Implementation details

TBD




Discussion / hints:
We must address this issue before the first stable release because all three options require non-backward compatible changes.
:+1: to option 1.

Another option is to do what UR does and create a "chain" (linked list) of parameters.
```
struct foo_param {
  param_type type;
  int foo;
  void *next;
}

struct bar_param {
  param_type type;
  int bar;
  void *next;
}

struct foo_param foo = { PARAM_FOO, 5, nullptr };
struct bar_param bar = { PARAM_BAR, 10, &foo };

someApi(&bar); // this would apply all options in the chain.

```

This is super verbose, but really flexible. One downside is that it's on the api function to validate the parameters, and users need to remember to correctly set param type.

I also vote for option 1. What UR does is also OK but from my experience it's quite easy to forget setting the type (and we even had a bug in UMF where we set the type incorrectly).

One question: how's responsible for destroying the params - user or UMF?
> One question: how's responsible for destroying the params - user or UMF?

In the case of option 1, User calls `umfCreateFooParams()` function which internally allocates required data structure. After pool/provider is created user should call the appropriate `umfDestroyFooParams` function to tell UMF library that params data structure should be destroyed. 
Option 1 > Option 3 >>>> Option 2

~The same issue we have with other structures in public header. Also we have the same issue with other structures, like memprovider ops - in case of those structures probably option 3 (version field) is the way.~

Edit. I checked code and i found that we already have version in ops structure so it is fine. 

Fix patch:
diff --git a/benchmark/ubench.c b/benchmark/ubench.c
index 0ac174de6..645ddb743 100644
--- a/benchmark/ubench.c
+++ b/benchmark/ubench.c
@@ -421,11 +421,10 @@ static void do_ipc_get_put_benchmark(alloc_t *allocs, size_t num_allocs,
     }
 }
 
-int create_level_zero_params(level_zero_memory_provider_params_t *params) {
+int create_level_zero_params(ze_context_handle_t *context,
+                             ze_device_handle_t *device) {
     uint32_t driver_idx = 0;
     ze_driver_handle_t driver = NULL;
-    ze_context_handle_t context = NULL;
-    ze_device_handle_t device = NULL;
 
     int ret = init_level_zero();
     if (ret != 0) {
@@ -439,36 +438,68 @@ int create_level_zero_params(level_zero_memory_provider_params_t *params) {
         return ret;
     }
 
-    ret = create_context(driver, &context);
+    ret = create_context(driver, context);
     if (ret != 0) {
         fprintf(stderr, "Failed to create L0 context!\n");
         return ret;
     }
 
-    ret = find_gpu_device(driver, &device);
-    if (ret || device == NULL) {
+    ret = find_gpu_device(driver, device);
+    if (ret) {
         fprintf(stderr, "Cannot find GPU device!\n");
-        destroy_context(context);
+        destroy_context(*context);
         return ret;
     }
 
-    params->level_zero_context_handle = context;
-    params->level_zero_device_handle = device;
-    params->memory_type = UMF_MEMORY_TYPE_DEVICE;
-
     return ret;
 }
 
 UBENCH_EX(ipc, disjoint_pool_with_level_zero_provider) {
     const size_t BUFFER_SIZE = 100;
     const size_t N_BUFFERS = 1000;
-    level_zero_memory_provider_params_t level_zero_params = {0};
+    umf_result_t umf_result;
+    ze_context_handle_t context = NULL;
+    ze_device_handle_t device = NULL;
+    umf_level_zero_memory_provider_params_handle_t level_zero_params = NULL;
 
-    int ret = create_level_zero_params(&level_zero_params);
+    int ret = create_level_zero_params(&context, &device);
     if (ret != 0) {
+        fprintf(stderr, "error: create_level_zero_params() failed\n");
         exit(-1);
     }
 
+    umf_result = umfLevelZeroMemoryProviderParamsCreate(&level_zero_params);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "error: umfLevelZeroMemoryProviderParamsCreate() failed\n");
+        goto err_destroy_context;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetContext(level_zero_params, context);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "error: umfLevelZeroMemoryProviderParamsSetContext() failed\n");
+        goto err_destroy_params;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetDevice(level_zero_params, device);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "error: umfLevelZeroMemoryProviderParamsSetDevice() failed\n");
+        goto err_destroy_params;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetMemoryType(
+        level_zero_params, UMF_MEMORY_TYPE_DEVICE);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(
+            stderr,
+            "error: umfLevelZeroMemoryProviderParamsSetMemoryType() failed\n");
+        goto err_destroy_params;
+    }
+
     alloc_t *allocs = alloc_array(N_BUFFERS);
     if (allocs == NULL) {
         fprintf(stderr, "error: alloc_array() failed\n");
@@ -481,10 +512,9 @@ UBENCH_EX(ipc, disjoint_pool_with_level_zero_provider) {
         goto err_free_allocs;
     }
 
-    umf_result_t umf_result;
     umf_memory_provider_handle_t provider = NULL;
     umf_result = umfMemoryProviderCreate(umfLevelZeroMemoryProviderOps(),
-                                         &level_zero_params, &provider);
+                                         level_zero_params, &provider);
     if (umf_result != UMF_RESULT_SUCCESS) {
         fprintf(stderr, "error: umfMemoryProviderCreate() failed\n");
         goto err_free_ipc_handles;
@@ -570,8 +600,11 @@ UBENCH_EX(ipc, disjoint_pool_with_level_zero_provider) {
 err_free_allocs:
     free(allocs);
 
+err_destroy_params:
+    umfLevelZeroMemoryProviderParamsDestroy(level_zero_params);
+
 err_destroy_context:
-    destroy_context(level_zero_params.level_zero_context_handle);
+    destroy_context(context);
 }
 #endif /* (defined UMF_BUILD_LIBUMF_POOL_DISJOINT && defined UMF_BUILD_LEVEL_ZERO_PROVIDER && defined UMF_BUILD_GPU_TESTS) */
 
diff --git a/examples/ipc_level_zero/ipc_level_zero.c b/examples/ipc_level_zero/ipc_level_zero.c
index cfab57b0d..fc93eb930 100644
--- a/examples/ipc_level_zero/ipc_level_zero.c
+++ b/examples/ipc_level_zero/ipc_level_zero.c
@@ -20,15 +20,48 @@ int create_level_zero_pool(ze_context_handle_t context,
                            ze_device_handle_t device,
                            umf_memory_pool_handle_t *pool) {
     // setup params
-    level_zero_memory_provider_params_t params = {0};
-    params.level_zero_context_handle = context;
-    params.level_zero_device_handle = device;
-    params.memory_type = UMF_MEMORY_TYPE_DEVICE;
+    umf_level_zero_memory_provider_params_handle_t provider_params = NULL;
+
+    umf_result_t umf_result =
+        umfLevelZeroMemoryProviderParamsCreate(&provider_params);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "ERROR: Failed to create Level Zero memory provider params!\n");
+        return -1;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetContext(provider_params, context);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "ERROR: Failed to set context in Level Zero memory "
+                        "provider params!\n");
+        umfLevelZeroMemoryProviderParamsDestroy(provider_params);
+        return -1;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetDevice(provider_params, device);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "ERROR: Failed to set device in Level Zero memory "
+                        "provider params!\n");
+        umfLevelZeroMemoryProviderParamsDestroy(provider_params);
+        return -1;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetMemoryType(
+        provider_params, UMF_MEMORY_TYPE_DEVICE);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "ERROR: Failed to set memory type in Level Zero memory "
+                        "provider params!\n");
+        umfLevelZeroMemoryProviderParamsDestroy(provider_params);
+        return -1;
+    }
 
     // create Level Zero provider
     umf_memory_provider_handle_t provider = 0;
-    umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+    umf_result = umfMemoryProviderCreate(umfLevelZeroMemoryProviderOps(),
+                                         provider_params, &provider);
+    umfLevelZeroMemoryProviderParamsDestroy(provider_params);
     if (umf_result != UMF_RESULT_SUCCESS) {
         fprintf(stderr,
                 "ERROR: Failed to create Level Zero memory provider!\n");
diff --git a/examples/level_zero_shared_memory/level_zero_shared_memory.c b/examples/level_zero_shared_memory/level_zero_shared_memory.c
index 1a38beceb..d7f68168d 100644
--- a/examples/level_zero_shared_memory/level_zero_shared_memory.c
+++ b/examples/level_zero_shared_memory/level_zero_shared_memory.c
@@ -49,22 +49,51 @@ int main(void) {
 
     // Setup parameters for the Level Zero memory provider. It will be used for
     // allocating memory from Level Zero devices.
-    level_zero_memory_provider_params_t ze_memory_provider_params = {0};
-    ze_memory_provider_params.level_zero_context_handle = hContext;
-    ze_memory_provider_params.level_zero_device_handle = hDevice;
+    umf_level_zero_memory_provider_params_handle_t ze_memory_provider_params =
+        NULL;
+    res = umfLevelZeroMemoryProviderParamsCreate(&ze_memory_provider_params);
+    if (res != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to create memory provider params!\n");
+        ret = -1;
+        goto level_zero_destroy;
+    }
+
+    res = umfLevelZeroMemoryProviderParamsSetContext(ze_memory_provider_params,
+                                                     hContext);
+    if (res != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to set context in memory provider params!\n");
+        ret = -1;
+        goto provider_params_destroy;
+    }
+
+    res = umfLevelZeroMemoryProviderParamsSetDevice(ze_memory_provider_params,
+                                                    hDevice);
+    if (res != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to set device in memory provider params!\n");
+        ret = -1;
+        goto provider_params_destroy;
+    }
+
     // Set the memory type to shared to allow the memory to be accessed on both
     // CPU and GPU.
-    ze_memory_provider_params.memory_type = UMF_MEMORY_TYPE_SHARED;
+    res = umfLevelZeroMemoryProviderParamsSetMemoryType(
+        ze_memory_provider_params, UMF_MEMORY_TYPE_SHARED);
+    if (res != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to set memory type in memory provider params!\n");
+        ret = -1;
+        goto provider_params_destroy;
+    }
 
     // Create Level Zero memory provider
     umf_memory_provider_handle_t ze_memory_provider;
-    res = umfMemoryProviderCreate(umfLevelZeroMemoryProviderOps(),
-                                  &ze_memory_provider_params,
-                                  &ze_memory_provider);
+    res =
+        umfMemoryProviderCreate(umfLevelZeroMemoryProviderOps(),
+                                ze_memory_provider_params, &ze_memory_provider);
     if (res != UMF_RESULT_SUCCESS) {
         fprintf(stderr, "Failed to create a memory provider!\n");
         ret = -1;
-        goto level_zero_destroy;
+        goto provider_params_destroy;
     }
 
     printf("Level Zero memory provider created at %p\n",
@@ -154,6 +183,9 @@ int main(void) {
 memory_provider_destroy:
     umfMemoryProviderDestroy(ze_memory_provider);
 
+provider_params_destroy:
+    umfLevelZeroMemoryProviderParamsDestroy(ze_memory_provider_params);
+
 level_zero_destroy:
     ret = destroy_context(hContext);
     return ret;
diff --git a/include/umf/providers/provider_level_zero.h b/include/umf/providers/provider_level_zero.h
index b3cc02851..f760c5724 100644
--- a/include/umf/providers/provider_level_zero.h
+++ b/include/umf/providers/provider_level_zero.h
@@ -17,20 +17,56 @@ extern "C" {
 typedef struct _ze_device_handle_t *ze_device_handle_t;
 typedef struct _ze_context_handle_t *ze_context_handle_t;
 
-/// @brief Level Zero Memory Provider settings struct
-typedef struct level_zero_memory_provider_params_t {
-    ze_context_handle_t
-        level_zero_context_handle; ///< Handle to the Level Zero context
-    ze_device_handle_t
-        level_zero_device_handle; ///< Handle to the Level Zero device
-
-    umf_usm_memory_type_t memory_type; ///< Allocation memory type
-
-    ze_device_handle_t *
-        resident_device_handles; ///< Array of devices for which the memory should be made resident
-    uint32_t
-        resident_device_count; ///< Number of devices for which the memory should be made resident
-} level_zero_memory_provider_params_t;
+struct umf_level_zero_memory_provider_params_t;
+
+/// @brief handle to the parameters of the Level Zero Memory Provider.
+typedef struct umf_level_zero_memory_provider_params_t
+    *umf_level_zero_memory_provider_params_handle_t;
+
+/// @brief  Create a struct to store parameters of the Level Zero Memory Provider.
+/// @param  hParams [out] handle to the newly created parameters struct.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsCreate(
+    umf_level_zero_memory_provider_params_handle_t *hParams);
+
+/// @brief  Destroy parameters struct.
+/// @param  hParams handle to the parameters of the Level Zero Memory Provider.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsDestroy(
+    umf_level_zero_memory_provider_params_handle_t hParams);
+
+/// @brief  Set the Level Zero context handle in the parameters struct.
+/// @param  hParams handle to the parameters of the Level Zero Memory Provider.
+/// @param  hContext handle to the Level Zero context. Cannot be \p NULL.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsSetContext(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_context_handle_t hContext);
+
+/// @brief  Set the Level Zero device handle in the parameters struct.
+/// @param  hParams handle to the parameters of the Level Zero Memory Provider.
+/// @param  hDevice handle to the Level Zero device. Can be \p NULL if memory type is \p UMF_MEMORY_TYPE_HOST.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsSetDevice(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t hDevice);
+
+/// @brief  Set the memory type in the parameters struct.
+/// @param  hParams handle to the parameters of the Level Zero Memory Provider.
+/// @param  memoryType memory type.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsSetMemoryType(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    umf_usm_memory_type_t memoryType);
+
+/// @brief  Set the resident devices in the parameters struct.
+/// @param  hParams handle to the parameters of the Level Zero Memory Provider.
+/// @param  hDevices array of devices for which the memory should be made resident.
+/// @param  deviceCount number of devices for which the memory should be made resident.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfLevelZeroMemoryProviderParamsSetResidentDevices(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t *hDevices, uint32_t deviceCount);
 
 umf_memory_provider_ops_t *umfLevelZeroMemoryProviderOps(void);
 
diff --git a/src/libumf.def b/src/libumf.def
index 8f29f4579..42a924bd7 100644
--- a/src/libumf.def
+++ b/src/libumf.def
@@ -28,6 +28,12 @@ EXPORTS
     umfGetIPCHandle
     umfGetLastFailedMemoryProvider
     umfLevelZeroMemoryProviderOps
+    umfLevelZeroMemoryProviderParamsCreate
+    umfLevelZeroMemoryProviderParamsDestroy
+    umfLevelZeroMemoryProviderParamsSetContext
+    umfLevelZeroMemoryProviderParamsSetDevice
+    umfLevelZeroMemoryProviderParamsSetMemoryType
+    umfLevelZeroMemoryProviderParamsSetResidentDevices
     umfMemoryProviderAlloc
     umfMemoryProviderAllocationMerge
     umfMemoryProviderAllocationSplit
diff --git a/src/libumf.map b/src/libumf.map
index f70d247b5..443194e55 100644
--- a/src/libumf.map
+++ b/src/libumf.map
@@ -22,6 +22,12 @@ UMF_1.0 {
         umfGetIPCHandle;
         umfGetLastFailedMemoryProvider;
         umfLevelZeroMemoryProviderOps;
+        umfLevelZeroMemoryProviderParamsCreate;
+        umfLevelZeroMemoryProviderParamsDestroy;
+        umfLevelZeroMemoryProviderParamsSetContext;
+        umfLevelZeroMemoryProviderParamsSetDevice;
+        umfLevelZeroMemoryProviderParamsSetMemoryType;
+        umfLevelZeroMemoryProviderParamsSetResidentDevices;
         umfMemoryProviderAlloc;
         umfMemoryProviderAllocationMerge;
         umfMemoryProviderAllocationSplit;
diff --git a/src/provider/provider_level_zero.c b/src/provider/provider_level_zero.c
index 6b4468da6..f4a3e97c2 100644
--- a/src/provider/provider_level_zero.c
+++ b/src/provider/provider_level_zero.c
@@ -16,6 +16,51 @@
 
 #if defined(UMF_NO_LEVEL_ZERO_PROVIDER)
 
+umf_result_t umfLevelZeroMemoryProviderParamsCreate(
+    umf_level_zero_memory_provider_params_handle_t *hParams) {
+    (void)hParams;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsDestroy(
+    umf_level_zero_memory_provider_params_handle_t hParams) {
+    (void)hParams;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetContext(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_context_handle_t hContext) {
+    (void)hParams;
+    (void)hContext;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetDevice(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t hDevice) {
+    (void)hParams;
+    (void)hDevice;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetMemoryType(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    umf_usm_memory_type_t memoryType) {
+    (void)hParams;
+    (void)memoryType;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetResidentDevices(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t *hDevices, uint32_t deviceCount) {
+    (void)hParams;
+    (void)hDevices;
+    (void)deviceCount;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
 umf_memory_provider_ops_t *umfLevelZeroMemoryProviderOps(void) {
     // not supported
     return NULL;
@@ -24,6 +69,7 @@ umf_memory_provider_ops_t *umfLevelZeroMemoryProviderOps(void) {
 #else // !defined(UMF_NO_LEVEL_ZERO_PROVIDER)
 
 #include "base_alloc_global.h"
+#include "libumf.h"
 #include "utils_assert.h"
 #include "utils_common.h"
 #include "utils_concurrency.h"
@@ -32,6 +78,21 @@ umf_memory_provider_ops_t *umfLevelZeroMemoryProviderOps(void) {
 #include "utils_sanitizers.h"
 #include "ze_api.h"
 
+// Level Zero Memory Provider settings struct
+typedef struct umf_level_zero_memory_provider_params_t {
+    ze_context_handle_t
+        level_zero_context_handle; ///< Handle to the Level Zero context
+    ze_device_handle_t
+        level_zero_device_handle; ///< Handle to the Level Zero device
+
+    umf_usm_memory_type_t memory_type; ///< Allocation memory type
+
+    ze_device_handle_t *
+        resident_device_handles; ///< Array of devices for which the memory should be made resident
+    uint32_t
+        resident_device_count; ///< Number of devices for which the memory should be made resident
+} umf_level_zero_memory_provider_params_t;
+
 typedef struct ze_memory_provider_t {
     ze_context_handle_t context;
     ze_device_handle_t device;
@@ -134,26 +195,127 @@ static void init_ze_global_state(void) {
     }
 }
 
+umf_result_t umfLevelZeroMemoryProviderParamsCreate(
+    umf_level_zero_memory_provider_params_handle_t *hParams) {
+    libumfInit();
+    if (!hParams) {
+        LOG_ERR("Level zero memory provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    umf_level_zero_memory_provider_params_t *params =
+        umf_ba_global_alloc(sizeof(umf_level_zero_memory_provider_params_t));
+    if (!params) {
+        LOG_ERR("Cannot allocate memory for Level Zero memory provider params");
+        return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    // Assign default values
+    params->level_zero_context_handle = NULL;
+    params->level_zero_device_handle = NULL;
+    params->memory_type = UMF_MEMORY_TYPE_UNKNOWN;
+    params->resident_device_handles = NULL;
+    params->resident_device_count = 0;
+
+    *hParams = params;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsDestroy(
+    umf_level_zero_memory_provider_params_handle_t hParams) {
+    umf_ba_global_free(hParams);
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetContext(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_context_handle_t hContext) {
+    if (!hParams) {
+        LOG_ERR("Level zero memory provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (!hContext) {
+        LOG_ERR("Level zero context handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->level_zero_context_handle = hContext;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetDevice(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t hDevice) {
+    if (!hParams) {
+        LOG_ERR("Level zero memory provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->level_zero_device_handle = hDevice;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetMemoryType(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    umf_usm_memory_type_t memoryType) {
+    if (!hParams) {
+        LOG_ERR("Level zero memory provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->memory_type = memoryType;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfLevelZeroMemoryProviderParamsSetResidentDevices(
+    umf_level_zero_memory_provider_params_handle_t hParams,
+    ze_device_handle_t *hDevices, uint32_t deviceCount) {
+    if (!hParams) {
+        LOG_ERR("Level zero memory provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (deviceCount && !hDevices) {
+        LOG_ERR("Resident devices array is NULL, but deviceCount is not zero");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->resident_device_handles = hDevices;
+    hParams->resident_device_count = deviceCount;
+
+    return UMF_RESULT_SUCCESS;
+}
+
 static umf_result_t ze_memory_provider_initialize(void *params,
                                                   void **provider) {
     if (params == NULL) {
         return UMF_RESULT_ERROR_INVALID_ARGUMENT;
     }
 
-    level_zero_memory_provider_params_t *ze_params =
-        (level_zero_memory_provider_params_t *)params;
+    umf_level_zero_memory_provider_params_handle_t ze_params =
+        (umf_level_zero_memory_provider_params_handle_t)params;
 
     if (!ze_params->level_zero_context_handle) {
+        LOG_ERR("Level Zero context handle is NULL");
         return UMF_RESULT_ERROR_INVALID_ARGUMENT;
     }
 
     if ((ze_params->memory_type == UMF_MEMORY_TYPE_HOST) ==
         (ze_params->level_zero_device_handle != NULL)) {
+        LOG_ERR("Level Zero device handle is NULL");
         return UMF_RESULT_ERROR_INVALID_ARGUMENT;
     }
 
-    if ((bool)ze_params->resident_device_count !=
-        (ze_params->resident_device_handles != NULL)) {
+    if ((bool)ze_params->resident_device_count &&
+        (ze_params->resident_device_handles == NULL)) {
+        LOG_ERR("Resident devices handles array is NULL, but device_count is "
+                "not zero");
         return UMF_RESULT_ERROR_INVALID_ARGUMENT;
     }
 
@@ -166,6 +328,7 @@ static umf_result_t ze_memory_provider_initialize(void *params,
     ze_memory_provider_t *ze_provider =
         umf_ba_global_alloc(sizeof(ze_memory_provider_t));
     if (!ze_provider) {
+        LOG_ERR("Cannot allocate memory for Level Zero Memory Provider");
         return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
     }
 
@@ -178,6 +341,7 @@ static umf_result_t ze_memory_provider_initialize(void *params,
             ze_provider->device, &ze_provider->device_properties));
 
         if (ret != UMF_RESULT_SUCCESS) {
+            LOG_ERR("Cannot get device properties");
             umf_ba_global_free(ze_provider);
             return ret;
         }
@@ -190,6 +354,7 @@ static umf_result_t ze_memory_provider_initialize(void *params,
         ze_provider->resident_device_handles = umf_ba_global_alloc(
             sizeof(ze_device_handle_t) * ze_params->resident_device_count);
         if (!ze_provider->resident_device_handles) {
+            LOG_ERR("Cannot allocate memory for resident devices");
             umf_ba_global_free(ze_provider);
             return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
         }


Test patch:
diff --git a/test/providers/ipc_level_zero_prov_common.c b/test/providers/ipc_level_zero_prov_common.c
index 11813653d..8b951cfc8 100644
--- a/test/providers/ipc_level_zero_prov_common.c
+++ b/test/providers/ipc_level_zero_prov_common.c
@@ -13,11 +13,9 @@
 #include <stdio.h>
 
 void memcopy(void *dst, const void *src, size_t size, void *context) {
-    level_zero_memory_provider_params_t *l0_params =
-        (level_zero_memory_provider_params_t *)context;
+    level_zero_copy_ctx_t *l0_params = (level_zero_copy_ctx_t *)context;
     int ret =
-        level_zero_copy(l0_params->level_zero_context_handle,
-                        l0_params->level_zero_device_handle, dst, src, size);
+        level_zero_copy(l0_params->context, l0_params->device, dst, src, size);
     if (ret != 0) {
         fprintf(stderr, "level_zero_copy failed with error %d\n", ret);
     }
diff --git a/test/providers/ipc_level_zero_prov_common.h b/test/providers/ipc_level_zero_prov_common.h
index dff51d08b..ea444133d 100644
--- a/test/providers/ipc_level_zero_prov_common.h
+++ b/test/providers/ipc_level_zero_prov_common.h
@@ -10,6 +10,13 @@
 
 #include <stddef.h>
 
+#include "ze_api.h"
+
+typedef struct level_zero_copy_ctx_t {
+    ze_context_handle_t context;
+    ze_device_handle_t device;
+} level_zero_copy_ctx_t;
+
 void memcopy(void *dst, const void *src, size_t size, void *context);
 
 #endif // UMF_TEST_IPC_LEVEL_ZERO_PROV_COMMON_H
diff --git a/test/providers/ipc_level_zero_prov_consumer.c b/test/providers/ipc_level_zero_prov_consumer.c
index 4ec952f4f..7fcb031cb 100644
--- a/test/providers/ipc_level_zero_prov_consumer.c
+++ b/test/providers/ipc_level_zero_prov_consumer.c
@@ -22,23 +22,88 @@ int main(int argc, char *argv[]) {
     }
 
     int port = atoi(argv[1]);
+    uint32_t driver_idx = 0;
+    ze_driver_handle_t hDriver = NULL;
+    ze_device_handle_t hDevice = NULL;
+    ze_context_handle_t hContext = NULL;
 
-    level_zero_memory_provider_params_t l0_params =
-        create_level_zero_prov_params(UMF_MEMORY_TYPE_DEVICE);
+    int ret = find_driver_with_gpu(&driver_idx, &hDriver);
+    if (ret != 0 || hDriver == NULL) {
+        fprintf(stderr, "find_driver_with_gpu() failed!\n");
+        return -1;
+    }
+
+    ret = find_gpu_device(hDriver, &hDevice);
+    if (ret != 0 || hDevice == NULL) {
+        fprintf(stderr, "find_gpu_device() failed!\n");
+        return -1;
+    }
+
+    ret = create_context(hDriver, &hContext);
+    if (ret != 0) {
+        fprintf(stderr, "create_context() failed!\n");
+        return -1;
+    }
+
+    umf_level_zero_memory_provider_params_handle_t l0_params = NULL;
+    umf_result_t umf_result =
+        umfLevelZeroMemoryProviderParamsCreate(&l0_params);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to create Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_context;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetContext(l0_params, hContext);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(
+            stderr,
+            "Failed to set context in Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetDevice(l0_params, hDevice);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to set device in Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetMemoryType(
+        l0_params, UMF_MEMORY_TYPE_DEVICE);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to set memory type in Level Zero Memory "
+                        "Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
 
     umf_disjoint_pool_params_handle_t pool_params = NULL;
 
-    umf_result_t umf_result = umfDisjointPoolParamsCreate(&pool_params);
+    umf_result = umfDisjointPoolParamsCreate(&pool_params);
     if (umf_result != UMF_RESULT_SUCCESS) {
         fprintf(stderr, "Failed to create pool params!\n");
-        return -1;
+        ret = -1;
+        goto destroy_provider_params;
     }
 
-    int ret = run_consumer(port, umfDisjointPoolOps(), pool_params,
-                           umfLevelZeroMemoryProviderOps(), &l0_params, memcopy,
-                           &l0_params);
+    level_zero_copy_ctx_t copy_ctx = {hContext, hDevice};
+
+    ret = run_consumer(port, umfDisjointPoolOps(), pool_params,
+                       umfLevelZeroMemoryProviderOps(), l0_params, memcopy,
+                       &copy_ctx);
 
     umfDisjointPoolParamsDestroy(pool_params);
 
+destroy_provider_params:
+    umfLevelZeroMemoryProviderParamsDestroy(l0_params);
+
+destroy_context:
+    destroy_context(hContext);
+
     return ret;
 }
diff --git a/test/providers/ipc_level_zero_prov_producer.c b/test/providers/ipc_level_zero_prov_producer.c
index ba950c602..d9c672dee 100644
--- a/test/providers/ipc_level_zero_prov_producer.c
+++ b/test/providers/ipc_level_zero_prov_producer.c
@@ -22,23 +22,88 @@ int main(int argc, char *argv[]) {
     }
 
     int port = atoi(argv[1]);
+    uint32_t driver_idx = 0;
+    ze_driver_handle_t hDriver = NULL;
+    ze_device_handle_t hDevice = NULL;
+    ze_context_handle_t hContext = NULL;
 
-    level_zero_memory_provider_params_t l0_params =
-        create_level_zero_prov_params(UMF_MEMORY_TYPE_DEVICE);
+    int ret = find_driver_with_gpu(&driver_idx, &hDriver);
+    if (ret != 0 || hDriver == NULL) {
+        fprintf(stderr, "find_driver_with_gpu() failed!\n");
+        return -1;
+    }
+
+    ret = find_gpu_device(hDriver, &hDevice);
+    if (ret != 0 || hDevice == NULL) {
+        fprintf(stderr, "find_gpu_device() failed!\n");
+        return -1;
+    }
+
+    ret = create_context(hDriver, &hContext);
+    if (ret != 0) {
+        fprintf(stderr, "create_context() failed!\n");
+        return -1;
+    }
+
+    umf_level_zero_memory_provider_params_handle_t l0_params = NULL;
+    umf_result_t umf_result =
+        umfLevelZeroMemoryProviderParamsCreate(&l0_params);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to create Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_context;
+    }
+
+    umf_result =
+        umfLevelZeroMemoryProviderParamsSetContext(l0_params, hContext);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(
+            stderr,
+            "Failed to set context in Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetDevice(l0_params, hDevice);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to set device in Level Zero Memory Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
+
+    umf_result = umfLevelZeroMemoryProviderParamsSetMemoryType(
+        l0_params, UMF_MEMORY_TYPE_DEVICE);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to set memory type in Level Zero Memory "
+                        "Provider params!\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
 
     umf_disjoint_pool_params_handle_t pool_params = NULL;
 
-    umf_result_t umf_result = umfDisjointPoolParamsCreate(&pool_params);
+    umf_result = umfDisjointPoolParamsCreate(&pool_params);
     if (umf_result != UMF_RESULT_SUCCESS) {
         fprintf(stderr, "Failed to create pool params!\n");
-        return -1;
+        ret = -1;
+        goto destroy_provider_params;
     }
 
-    int ret = run_producer(port, umfDisjointPoolOps(), pool_params,
-                           umfLevelZeroMemoryProviderOps(), &l0_params, memcopy,
-                           &l0_params);
+    level_zero_copy_ctx_t copy_ctx = {hContext, hDevice};
+
+    ret = run_producer(port, umfDisjointPoolOps(), pool_params,
+                       umfLevelZeroMemoryProviderOps(), l0_params, memcopy,
+                       &copy_ctx);
 
     umfDisjointPoolParamsDestroy(pool_params);
 
+destroy_provider_params:
+    umfLevelZeroMemoryProviderParamsDestroy(l0_params);
+
+destroy_context:
+    destroy_context(hContext);
+
     return ret;
 }
diff --git a/test/providers/level_zero_helpers.cpp b/test/providers/level_zero_helpers.cpp
index 4cd993956..cd387ab91 100644
--- a/test/providers/level_zero_helpers.cpp
+++ b/test/providers/level_zero_helpers.cpp
@@ -63,9 +63,68 @@ struct libze_ops {
 } libze_ops;
 
 #if USE_DLOPEN
+// Generic no-op stub function for all callbacks
+template <typename... Args> ze_result_t noop_stub(Args &&...) {
+    return ZE_RESULT_SUCCESS; // Always return ZE_RESULT_SUCCESS
+}
+
 struct DlHandleCloser {
     void operator()(void *dlHandle) {
         if (dlHandle) {
+            // Reset all function pointers to no-op stubs in case the library
+            // but some other global object still try to call Level Zero functions.
+            libze_ops.zeInit = [](auto... args) { return noop_stub(args...); };
+            libze_ops.zeDriverGet = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeDeviceGet = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeDeviceGetProperties = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeContextCreate = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeContextDestroy = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandQueueCreate = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandQueueDestroy = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandQueueExecuteCommandLists = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandQueueSynchronize = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandListCreate = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandListDestroy = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandListClose = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandListAppendMemoryCopy = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeCommandListAppendMemoryFill = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeMemGetAllocProperties = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeMemAllocDevice = [](auto... args) {
+                return noop_stub(args...);
+            };
+            libze_ops.zeMemFree = [](auto... args) {
+                return noop_stub(args...);
+            };
             utils_close_library(dlHandle);
         }
     }
@@ -247,12 +306,34 @@ static int init_level_zero_lib(void) {
     return 0;
 }
 
+UTIL_ONCE_FLAG level_zero_init_flag;
+int InitResult;
+void init_level_zero_once() {
+    InitResult = InitLevelZeroOps();
+    if (InitResult != 0) {
+        return;
+    }
+    InitResult = init_level_zero_lib();
+}
+
+int init_level_zero() {
+    utils_init_once(&level_zero_init_flag, init_level_zero_once);
+
+    return InitResult;
+}
+
 int get_drivers(uint32_t *drivers_num_, ze_driver_handle_t **drivers_) {
     int ret = 0;
     ze_result_t ze_result;
     ze_driver_handle_t *drivers = NULL;
     uint32_t drivers_num = 0;
 
+    ret = init_level_zero();
+    if (ret != 0) {
+        fprintf(stderr, "init_level_zero() failed!\n");
+        goto init_fail;
+    }
+
     ze_result = libze_ops.zeDriverGet(&drivers_num, NULL);
     if (ze_result != ZE_RESULT_SUCCESS) {
         fprintf(stderr, "zeDriverGet() failed!\n");
@@ -288,6 +369,8 @@ int get_drivers(uint32_t *drivers_num_, ze_driver_handle_t **drivers_) {
         free(drivers);
         *drivers_ = NULL;
     }
+
+init_fail:
     return ret;
 }
 
@@ -298,6 +381,12 @@ int get_devices(ze_driver_handle_t driver, uint32_t *devices_num_,
     uint32_t devices_num = 0;
     ze_device_handle_t *devices = NULL;
 
+    ret = init_level_zero();
+    if (ret != 0) {
+        fprintf(stderr, "init_level_zero() failed!\n");
+        goto init_fail;
+    }
+
     ze_result = libze_ops.zeDeviceGet(driver, &devices_num, NULL);
     if (ze_result != ZE_RESULT_SUCCESS) {
         fprintf(stderr, "zeDeviceGet() failed!\n");
@@ -333,6 +422,7 @@ int get_devices(ze_driver_handle_t driver, uint32_t *devices_num_,
         free(devices);
         devices = NULL;
     }
+init_fail:
     return ret;
 }
 
@@ -649,66 +739,3 @@ ze_memory_type_t get_mem_type(ze_context_handle_t context, void *ptr) {
     libze_ops.zeMemGetAllocProperties(context, ptr, &alloc_props, &device);
     return alloc_props.type;
 }
-
-UTIL_ONCE_FLAG level_zero_init_flag;
-int InitResult;
-void init_level_zero_once() {
-    InitResult = InitLevelZeroOps();
-    if (InitResult != 0) {
-        return;
-    }
-    InitResult = init_level_zero_lib();
-}
-
-int init_level_zero() {
-    utils_init_once(&level_zero_init_flag, init_level_zero_once);
-
-    return InitResult;
-}
-
-level_zero_memory_provider_params_t
-create_level_zero_prov_params(umf_usm_memory_type_t memory_type) {
-    level_zero_memory_provider_params_t params = {
-        NULL, NULL, UMF_MEMORY_TYPE_UNKNOWN, NULL, 0};
-    uint32_t driver_idx = 0;
-    ze_driver_handle_t hDriver;
-    ze_device_handle_t hDevice;
-    ze_context_handle_t hContext;
-    int ret = -1;
-
-    ret = init_level_zero();
-    if (ret != 0) {
-        // Return empty params. Test will be skipped.
-        return params;
-    }
-
-    ret = find_driver_with_gpu(&driver_idx, &hDriver);
-    if (ret != 0 || hDriver == NULL) {
-        // Return empty params. Test will be skipped.
-        return params;
-    }
-
-    ret = find_gpu_device(hDriver, &hDevice);
-    if (ret != 0 || hDevice == NULL) {
-        // Return empty params. Test will be skipped.
-        return params;
-    }
-
-    ret = create_context(hDriver, &hContext);
-    if (ret != 0) {
-        // Return empty params. Test will be skipped.
-        return params;
-    }
-
-    params.level_zero_context_handle = hContext;
-
-    if (memory_type == UMF_MEMORY_TYPE_HOST) {
-        params.level_zero_device_handle = NULL;
-    } else {
-        params.level_zero_device_handle = hDevice;
-    }
-
-    params.memory_type = memory_type;
-
-    return params;
-}
\ No newline at end of file
diff --git a/test/providers/level_zero_helpers.h b/test/providers/level_zero_helpers.h
index 6cd452c1c..aa76f8f55 100644
--- a/test/providers/level_zero_helpers.h
+++ b/test/providers/level_zero_helpers.h
@@ -38,9 +38,6 @@ int destroy_context(ze_context_handle_t context);
 
 ze_memory_type_t get_mem_type(ze_context_handle_t context, void *ptr);
 
-level_zero_memory_provider_params_t
-create_level_zero_prov_params(umf_usm_memory_type_t memory_type);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/test/providers/provider_level_zero.cpp b/test/providers/provider_level_zero.cpp
index 347fa9888..06742d102 100644
--- a/test/providers/provider_level_zero.cpp
+++ b/test/providers/provider_level_zero.cpp
@@ -19,6 +19,92 @@
 using umf_test::test;
 using namespace umf_test;
 
+class LevelZeroTestHelper {
+  public:
+    LevelZeroTestHelper();
+
+    ~LevelZeroTestHelper() {
+        if (hContext_) {
+            destroy_context(hContext_);
+        }
+    }
+
+    ze_context_handle_t get_test_context() const { return hContext_; }
+
+    ze_device_handle_t get_test_device() const { return hDevice_; }
+
+  private:
+    ze_driver_handle_t hDriver_ = nullptr;
+    ze_context_handle_t hContext_ = nullptr;
+    ze_device_handle_t hDevice_ = nullptr;
+};
+
+LevelZeroTestHelper::LevelZeroTestHelper() {
+    uint32_t driver_idx = 0;
+
+    int ret = find_driver_with_gpu(&driver_idx, &hDriver_);
+    if (ret != 0 || hDriver_ == NULL) {
+        fprintf(stderr, "find_driver_with_gpu() failed!\n");
+        return;
+    }
+
+    ret = find_gpu_device(hDriver_, &hDevice_);
+    if (ret != 0 || hDevice_ == NULL) {
+        fprintf(stderr, "find_gpu_device() failed!\n");
+        return;
+    }
+
+    ret = create_context(hDriver_, &hContext_);
+    if (ret != 0) {
+        fprintf(stderr, "create_context() failed!\n");
+        return;
+    }
+}
+
+using level_zero_params_unique_handle_t =
+    std::unique_ptr<umf_level_zero_memory_provider_params_t,
+                    decltype(&umfLevelZeroMemoryProviderParamsDestroy)>;
+
+level_zero_params_unique_handle_t
+create_level_zero_prov_params(ze_context_handle_t context,
+                              ze_device_handle_t device,
+                              umf_usm_memory_type_t memory_type) {
+    umf_level_zero_memory_provider_params_handle_t params = nullptr;
+
+    umf_result_t res = umfLevelZeroMemoryProviderParamsCreate(&params);
+    if (res != UMF_RESULT_SUCCESS) {
+        return level_zero_params_unique_handle_t(
+            nullptr, &umfLevelZeroMemoryProviderParamsDestroy);
+    }
+
+    res = umfLevelZeroMemoryProviderParamsSetContext(params, context);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfLevelZeroMemoryProviderParamsDestroy(params);
+        return level_zero_params_unique_handle_t(
+            nullptr, &umfLevelZeroMemoryProviderParamsDestroy);
+        ;
+    }
+
+    res = umfLevelZeroMemoryProviderParamsSetDevice(params, device);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfLevelZeroMemoryProviderParamsDestroy(params);
+        return level_zero_params_unique_handle_t(
+            nullptr, &umfLevelZeroMemoryProviderParamsDestroy);
+        ;
+    }
+
+    res = umfLevelZeroMemoryProviderParamsSetMemoryType(params, memory_type);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfLevelZeroMemoryProviderParamsDestroy(params);
+        return level_zero_params_unique_handle_t(
+            nullptr, &umfLevelZeroMemoryProviderParamsDestroy);
+        ;
+    }
+
+    return level_zero_params_unique_handle_t(
+        params, &umfLevelZeroMemoryProviderParamsDestroy);
+}
+
 struct LevelZeroProviderInit
     : public test,
       public ::testing::WithParamInterface<umf_usm_memory_type_t> {};
@@ -28,18 +114,32 @@ INSTANTIATE_TEST_SUITE_P(, LevelZeroProviderInit,
                                            UMF_MEMORY_TYPE_DEVICE,
                                            UMF_MEMORY_TYPE_SHARED));
 
+LevelZeroTestHelper l0TestHelper;
+
 TEST_P(LevelZeroProviderInit, FailNullContext) {
     umf_memory_provider_ops_t *ops = umfLevelZeroMemoryProviderOps();
     ASSERT_NE(ops, nullptr);
 
     auto memory_type = GetParam();
 
-    level_zero_memory_provider_params_t params = {nullptr, nullptr, memory_type,
-                                                  nullptr, 0};
+    umf_level_zero_memory_provider_params_handle_t hParams = nullptr;
+    umf_result_t result = umfLevelZeroMemoryProviderParamsCreate(&hParams);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result =
+        umfLevelZeroMemoryProviderParamsSetMemoryType(hParams, memory_type);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result = umfLevelZeroMemoryProviderParamsSetDevice(
+        hParams, l0TestHelper.get_test_device());
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+
+    result = umfLevelZeroMemoryProviderParamsSetContext(hParams, nullptr);
+    ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
 
     umf_memory_provider_handle_t provider = nullptr;
-    umf_result_t result = umfMemoryProviderCreate(ops, &params, &provider);
+    result = umfMemoryProviderCreate(ops, hParams, &provider);
     ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umfLevelZeroMemoryProviderParamsDestroy(hParams);
 }
 
 TEST_P(LevelZeroProviderInit, FailNullDevice) {
@@ -51,12 +151,21 @@ TEST_P(LevelZeroProviderInit, FailNullDevice) {
     ASSERT_NE(ops, nullptr);
 
     auto memory_type = GetParam();
-    auto params = create_level_zero_prov_params(memory_type);
-    params.level_zero_device_handle = nullptr;
+    umf_level_zero_memory_provider_params_handle_t hParams = nullptr;
+    umf_result_t result = umfLevelZeroMemoryProviderParamsCreate(&hParams);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result =
+        umfLevelZeroMemoryProviderParamsSetMemoryType(hParams, memory_type);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result = umfLevelZeroMemoryProviderParamsSetContext(
+        hParams, l0TestHelper.get_test_context());
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
 
     umf_memory_provider_handle_t provider = nullptr;
-    umf_result_t result = umfMemoryProviderCreate(ops, &params, &provider);
+    result = umfMemoryProviderCreate(ops, hParams, &provider);
     ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umfLevelZeroMemoryProviderParamsDestroy(hParams);
 }
 
 TEST_F(test, FailNonNullDevice) {
@@ -65,41 +174,39 @@ TEST_F(test, FailNonNullDevice) {
 
     auto memory_type = UMF_MEMORY_TYPE_HOST;
 
-    // prepare params for device to get non-null device handle
-    auto params = create_level_zero_prov_params(UMF_MEMORY_TYPE_DEVICE);
-    params.memory_type = memory_type;
+    umf_level_zero_memory_provider_params_handle_t hParams = nullptr;
+    umf_result_t result = umfLevelZeroMemoryProviderParamsCreate(&hParams);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result =
+        umfLevelZeroMemoryProviderParamsSetMemoryType(hParams, memory_type);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result = umfLevelZeroMemoryProviderParamsSetContext(
+        hParams, l0TestHelper.get_test_context());
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
+    result = umfLevelZeroMemoryProviderParamsSetDevice(
+        hParams, l0TestHelper.get_test_device());
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
 
     umf_memory_provider_handle_t provider = nullptr;
-    umf_result_t result = umfMemoryProviderCreate(ops, &params, &provider);
+    result = umfMemoryProviderCreate(ops, hParams, &provider);
     ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umfLevelZeroMemoryProviderParamsDestroy(hParams);
 }
 
 TEST_F(test, FailMismatchedResidentHandlesCount) {
     umf_memory_provider_ops_t *ops = umfLevelZeroMemoryProviderOps();
     ASSERT_NE(ops, nullptr);
 
-    auto memory_type = UMF_MEMORY_TYPE_DEVICE;
-
-    auto params = create_level_zero_prov_params(memory_type);
-    params.resident_device_count = 99;
+    umf_level_zero_memory_provider_params_handle_t hParams = nullptr;
+    umf_result_t result = umfLevelZeroMemoryProviderParamsCreate(&hParams);
+    ASSERT_EQ(result, UMF_RESULT_SUCCESS);
 
-    umf_memory_provider_handle_t provider = nullptr;
-    umf_result_t result = umfMemoryProviderCreate(ops, &params, &provider);
+    result = umfLevelZeroMemoryProviderParamsSetResidentDevices(hParams,
+                                                                nullptr, 99);
     ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
-}
-
-TEST_F(test, FailMismatchedResidentHandlesPtr) {
-    umf_memory_provider_ops_t *ops = umfLevelZeroMemoryProviderOps();
-    ASSERT_NE(ops, nullptr);
-
-    auto memory_type = UMF_MEMORY_TYPE_DEVICE;
-
-    auto params = create_level_zero_prov_params(memory_type);
-    params.resident_device_handles = &params.level_zero_device_handle;
 
-    umf_memory_provider_handle_t provider = nullptr;
-    umf_result_t result = umfMemoryProviderCreate(ops, &params, &provider);
-    ASSERT_EQ(result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    umfLevelZeroMemoryProviderParamsDestroy(hParams);
 }
 
 class LevelZeroMemoryAccessor : public MemoryAccessor {
@@ -130,7 +237,8 @@ class LevelZeroMemoryAccessor : public MemoryAccessor {
 };
 
 using LevelZeroProviderTestParams =
-    std::tuple<level_zero_memory_provider_params_t, MemoryAccessor *>;
+    std::tuple<umf_level_zero_memory_provider_params_handle_t,
+               ze_context_handle_t, umf_usm_memory_type_t, MemoryAccessor *>;
 
 struct umfLevelZeroProviderTest
     : umf_test::test,
@@ -139,14 +247,14 @@ struct umfLevelZeroProviderTest
     void SetUp() override {
         test::SetUp();
 
-        auto [l0_params, accessor] = this->GetParam();
+        auto [l0_params, ze_context, memory_type, accessor] = this->GetParam();
         params = l0_params;
         memAccessor = accessor;
-        hContext = (ze_context_handle_t)params.level_zero_context_handle;
+        hContext = ze_context;
 
         ASSERT_NE(hContext, nullptr);
 
-        switch (params.memory_type) {
+        switch (memory_type) {
         case UMF_MEMORY_TYPE_DEVICE:
             zeMemoryTypeExpected = ZE_MEMORY_TYPE_DEVICE;
             break;
@@ -166,7 +274,7 @@ struct umfLevelZeroProviderTest
 
     void TearDown() override { test::TearDown(); }
 
-    level_zero_memory_provider_params_t params;
+    umf_level_zero_memory_provider_params_handle_t params;
     MemoryAccessor *memAccessor = nullptr;
     ze_context_handle_t hContext = nullptr;
     ze_memory_type_t zeMemoryTypeExpected = ZE_MEMORY_TYPE_UNKNOWN;
@@ -181,7 +289,7 @@ TEST_P(umfLevelZeroProviderTest, basic) {
     // create Level Zero provider
     umf_memory_provider_handle_t provider = nullptr;
     umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+        umfLevelZeroMemoryProviderOps(), params, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(provider, nullptr);
 
@@ -213,7 +321,7 @@ TEST_P(umfLevelZeroProviderTest, basic) {
 TEST_P(umfLevelZeroProviderTest, getPageSize) {
     umf_memory_provider_handle_t provider = nullptr;
     umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+        umfLevelZeroMemoryProviderOps(), params, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(provider, nullptr);
 
@@ -237,7 +345,7 @@ TEST_P(umfLevelZeroProviderTest, getPageSize) {
 TEST_P(umfLevelZeroProviderTest, getName) {
     umf_memory_provider_handle_t provider = nullptr;
     umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+        umfLevelZeroMemoryProviderOps(), params, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(provider, nullptr);
 
@@ -250,7 +358,7 @@ TEST_P(umfLevelZeroProviderTest, getName) {
 TEST_P(umfLevelZeroProviderTest, allocInvalidSize) {
     umf_memory_provider_handle_t provider = nullptr;
     umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+        umfLevelZeroMemoryProviderOps(), params, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(provider, nullptr);
 
@@ -272,14 +380,14 @@ TEST_P(umfLevelZeroProviderTest, providerCreateInvalidArgs) {
         umfLevelZeroMemoryProviderOps(), nullptr, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
 
-    umf_result = umfMemoryProviderCreate(nullptr, &params, nullptr);
+    umf_result = umfMemoryProviderCreate(nullptr, params, nullptr);
     ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
 }
 
 TEST_P(umfLevelZeroProviderTest, getPageSizeInvalidArgs) {
     umf_memory_provider_handle_t provider = nullptr;
     umf_result_t umf_result = umfMemoryProviderCreate(
-        umfLevelZeroMemoryProviderOps(), &params, &provider);
+        umfLevelZeroMemoryProviderOps(), params, &provider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(provider, nullptr);
 
@@ -292,36 +400,62 @@ TEST_P(umfLevelZeroProviderTest, getPageSizeInvalidArgs) {
     umfMemoryProviderDestroy(provider);
 }
 
-// TODO add tests that mixes Level Zero Memory Provider and Disjoint Pool
+TEST_P(umfLevelZeroProviderTest, levelZeroProviderNullParams) {
+    umf_result_t res = umfLevelZeroMemoryProviderParamsCreate(nullptr);
+    EXPECT_EQ(res, UMF_RESULT_ERROR_INVALID_ARGUMENT);
 
-level_zero_memory_provider_params_t l0Params_device_memory =
-    create_level_zero_prov_params(UMF_MEMORY_TYPE_DEVICE);
-level_zero_memory_provider_params_t l0Params_shared_memory =
-    create_level_zero_prov_params(UMF_MEMORY_TYPE_SHARED);
-level_zero_memory_provider_params_t l0Params_host_memory =
-    create_level_zero_prov_params(UMF_MEMORY_TYPE_HOST);
+    res = umfLevelZeroMemoryProviderParamsSetContext(nullptr, hContext);
+    EXPECT_EQ(res, UMF_RESULT_ERROR_INVALID_ARGUMENT);
 
-LevelZeroMemoryAccessor l0Accessor(
-    (ze_context_handle_t)l0Params_device_memory.level_zero_context_handle,
-    (ze_device_handle_t)l0Params_device_memory.level_zero_device_handle);
+    res = umfLevelZeroMemoryProviderParamsSetDevice(nullptr, nullptr);
+    EXPECT_EQ(res, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    res = umfLevelZeroMemoryProviderParamsSetMemoryType(nullptr,
+                                                        UMF_MEMORY_TYPE_DEVICE);
+    EXPECT_EQ(res, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+}
+
+// TODO add tests that mixes Level Zero Memory Provider and Disjoint Pool
+
+level_zero_params_unique_handle_t l0Params_device_memory =
+    create_level_zero_prov_params(l0TestHelper.get_test_context(),
+                                  l0TestHelper.get_test_device(),
+                                  UMF_MEMORY_TYPE_DEVICE);
+level_zero_params_unique_handle_t l0Params_shared_memory =
+    create_level_zero_prov_params(l0TestHelper.get_test_context(),
+                                  l0TestHelper.get_test_device(),
+                                  UMF_MEMORY_TYPE_SHARED);
+level_zero_params_unique_handle_t l0Params_host_memory =
+    create_level_zero_prov_params(l0TestHelper.get_test_context(), nullptr,
+                                  UMF_MEMORY_TYPE_HOST);
+
+LevelZeroMemoryAccessor
+    l0Accessor((ze_context_handle_t)l0TestHelper.get_test_context(),
+               (ze_device_handle_t)l0TestHelper.get_test_device());
 
 HostMemoryAccessor hostAccessor;
 
 INSTANTIATE_TEST_SUITE_P(
     umfLevelZeroProviderTestSuite, umfLevelZeroProviderTest,
     ::testing::Values(
-        LevelZeroProviderTestParams{l0Params_device_memory, &l0Accessor},
-        LevelZeroProviderTestParams{l0Params_shared_memory, &hostAccessor},
-        LevelZeroProviderTestParams{l0Params_host_memory, &hostAccessor}));
+        LevelZeroProviderTestParams{l0Params_device_memory.get(),
+                                    l0TestHelper.get_test_context(),
+                                    UMF_MEMORY_TYPE_DEVICE, &l0Accessor},
+        LevelZeroProviderTestParams{l0Params_shared_memory.get(),
+                                    l0TestHelper.get_test_context(),
+                                    UMF_MEMORY_TYPE_SHARED, &hostAccessor},
+        LevelZeroProviderTestParams{l0Params_host_memory.get(),
+                                    l0TestHelper.get_test_context(),
+                                    UMF_MEMORY_TYPE_HOST, &hostAccessor}));
 
 // TODO: it looks like there is some problem with IPC implementation in Level
 // Zero on windows. Issue: #494
 #ifdef _WIN32
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(umfIpcTest);
 #else
-INSTANTIATE_TEST_SUITE_P(umfLevelZeroProviderTestSuite, umfIpcTest,
-                         ::testing::Values(ipcTestParams{
-                             umfProxyPoolOps(), nullptr,
-                             umfLevelZeroMemoryProviderOps(),
-                             &l0Params_device_memory, &l0Accessor, false}));
+INSTANTIATE_TEST_SUITE_P(
+    umfLevelZeroProviderTestSuite, umfIpcTest,
+    ::testing::Values(ipcTestParams{
+        umfProxyPoolOps(), nullptr, umfLevelZeroMemoryProviderOps(),
+        l0Params_device_memory.get(), &l0Accessor, false}));
 #endif